menuitemradio
A checkable menuitem in a group where only one item can be checked at a time. Perfect for mutually exclusive options like theme selection, sort order, or view modes.
Overview
The menuitemradio role defines a menu item that is part of a group of items with the same role, where only one item in the group can be checked at a time—similar to how radio buttons work in forms.
Unlike menuitemcheckbox where multiple items can be selected, menuitemradio enforces mutual exclusivity within its group:
aria-checked="true"- This item is selected (only one per group)aria-checked="false"- This item is not selected
When to Use menuitemradio
Use menuitemradio when you have a set of mutually exclusive options in a dropdown menu. Common examples include font size selection, theme switching, sort order options, and view mode selection.
Live Demo: Interactive Radio Menus
Font Size Selection
Theme Selection
Sort Order Selection
Try with keyboard: Open a menu, then use ↑/↓ to navigate, Enter or Space to select, and Escape to close. Notice how only one option can be selected at a time!
Code Examples
Basic Usage
<!-- Basic menuitemradio in a dropdown menu -->
<button
aria-haspopup="menu"
aria-expanded="false"
id="font-button"
>
Font Size
</button>
<div role="menu" aria-labelledby="font-button">
<div
role="menuitemradio"
aria-checked="false"
tabindex="-1"
>
Small
</div>
<div
role="menuitemradio"
aria-checked="true"
tabindex="-1"
>
Medium
</div>
<div
role="menuitemradio"
aria-checked="false"
tabindex="-1"
>
Large
</div>
</div>
<!--
Note: Only ONE menuitemradio in a group should have
aria-checked="true" at any time (mutually exclusive)
-->Multiple Radio Groups
<!-- Multiple radio groups in one menu -->
<div role="menu" aria-label="Document Settings">
<!-- Group 1: Font Size -->
<div role="group" aria-label="Font Size">
<div role="presentation" class="group-label">Font Size</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Small
</div>
<div role="menuitemradio" aria-checked="true" tabindex="-1">
Medium
</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Large
</div>
</div>
<div role="separator"></div>
<!-- Group 2: Line Spacing -->
<div role="group" aria-label="Line Spacing">
<div role="presentation" class="group-label">Line Spacing</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Single
</div>
<div role="menuitemradio" aria-checked="true" tabindex="-1">
1.5 Lines
</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Double
</div>
</div>
</div>
<!--
Use role="group" with aria-label to create
semantic groups within a menu. Each group
maintains its own selection independently.
-->Keyboard Support
<!-- Accessible menuitemradio with keyboard support -->
<script>
const menu = document.querySelector('[role="menu"]');
const items = menu.querySelectorAll('[role="menuitemradio"]');
let currentIndex = 0;
menu.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
currentIndex = (currentIndex + 1) % items.length;
items[currentIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
currentIndex = (currentIndex - 1 + items.length) % items.length;
items[currentIndex].focus();
break;
case 'Home':
e.preventDefault();
currentIndex = 0;
items[currentIndex].focus();
break;
case 'End':
e.preventDefault();
currentIndex = items.length - 1;
items[currentIndex].focus();
break;
case 'Enter':
case ' ':
e.preventDefault();
selectRadioItem(items[currentIndex]);
break;
case 'Escape':
e.preventDefault();
closeMenu();
break;
}
});
function selectRadioItem(selectedItem) {
// Get all items in the same group
const group = selectedItem.closest('[role="group"]') || menu;
const groupItems = group.querySelectorAll('[role="menuitemradio"]');
// Deselect all items in group
groupItems.forEach(item => {
item.setAttribute('aria-checked', 'false');
});
// Select the clicked item
selectedItem.setAttribute('aria-checked', 'true');
}
</script>React Component
// React menuitemradio Component
import { useState, useRef, useEffect } from 'react';
interface RadioMenuItem {
id: string;
label: string;
value: string;
}
interface MenuRadioGroupProps {
label: string;
items: RadioMenuItem[];
selectedValue: string;
onChange: (value: string) => void;
}
function MenuRadioGroup({ label, items, selectedValue, onChange }: MenuRadioGroupProps) {
const [isOpen, setIsOpen] = useState(false);
const [focusedIndex, setFocusedIndex] = useState(0);
const menuRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const selectedItem = items.find(i => i.id === selectedValue);
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setFocusedIndex(prev => (prev + 1) % items.length);
break;
case 'ArrowUp':
e.preventDefault();
setFocusedIndex(prev =>
(prev - 1 + items.length) % items.length
);
break;
case 'Enter':
case ' ':
e.preventDefault();
onChange(items[focusedIndex].id);
break;
case 'Escape':
setIsOpen(false);
buttonRef.current?.focus();
break;
}
};
useEffect(() => {
if (isOpen) {
// Focus the selected item when menu opens
const selectedIndex = items.findIndex(i => i.id === selectedValue);
setFocusedIndex(selectedIndex >= 0 ? selectedIndex : 0);
menuRef.current?.focus();
}
}, [isOpen, items, selectedValue]);
return (
<div className="relative">
<button
ref={buttonRef}
aria-haspopup="menu"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
className="px-4 py-2 bg-gray-800 rounded flex items-center gap-2"
>
{label}: {selectedItem?.label}
<ChevronIcon />
</button>
{isOpen && (
<div
ref={menuRef}
role="menu"
aria-label={label}
tabIndex={-1}
onKeyDown={handleKeyDown}
className="absolute mt-1 bg-gray-900 rounded shadow-lg"
>
{items.map((item, index) => (
<div
key={item.id}
role="menuitemradio"
aria-checked={item.id === selectedValue}
tabIndex={index === focusedIndex ? 0 : -1}
onClick={() => {
onChange(item.id);
setIsOpen(false);
}}
className={`px-4 py-2 flex items-center gap-2 cursor-pointer
${index === focusedIndex ? 'bg-purple-600/30' : 'hover:bg-white/10'}
${item.id === selectedValue ? 'text-purple-300' : 'text-white'}`}
>
<span className="w-4 h-4 rounded-full border-2 flex items-center justify-center
${item.id === selectedValue ? 'border-purple-400' : 'border-gray-500'}">
{item.id === selectedValue && (
<span className="w-2 h-2 rounded-full bg-purple-400" />
)}
</span>
{item.label}
</div>
))}
</div>
)}
</div>
);
}
// Usage
function App() {
const [fontSize, setFontSize] = useState('medium');
return (
<MenuRadioGroup
label="Font Size"
items={[
{ id: 'small', label: 'Small', value: '12px' },
{ id: 'medium', label: 'Medium', value: '16px' },
{ id: 'large', label: 'Large', value: '20px' },
]}
selectedValue={fontSize}
onChange={setFontSize}
/>
);
}Mixed with Checkboxes
<!-- Menu with both menuitemcheckbox and menuitemradio -->
<div role="menu" aria-label="View Settings">
<!-- Checkbox items (multiple can be selected) -->
<div role="group" aria-label="Panels">
<div role="presentation">Show Panels</div>
<div role="menuitemcheckbox" aria-checked="true" tabindex="-1">
Sidebar
</div>
<div role="menuitemcheckbox" aria-checked="true" tabindex="-1">
Toolbar
</div>
<div role="menuitemcheckbox" aria-checked="false" tabindex="-1">
Status Bar
</div>
</div>
<div role="separator"></div>
<!-- Radio items (only one can be selected) -->
<div role="group" aria-label="View Mode">
<div role="presentation">View Mode</div>
<div role="menuitemradio" aria-checked="true" tabindex="-1">
List View
</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Grid View
</div>
<div role="menuitemradio" aria-checked="false" tabindex="-1">
Compact View
</div>
</div>
</div>
<!--
Key difference:
- menuitemcheckbox: Multiple items can be checked (independent)
- menuitemradio: Only ONE item per group can be checked (mutually exclusive)
-->Keyboard Support
Required & Supported Attributes
Required Attributes
aria-checkedRequired. Indicates the current selection state. Must be true or false. Unlike menuitemcheckbox, the mixed value is NOT valid for menuitemradio.
Supported Attributes
aria-checkedRequiredRequired. Selection state (true/false only)
aria-disabledIndicates item is disabled and not selectable
aria-labelAccessible name for the menu item
aria-labelledbyReferences element(s) that label the item
aria-describedbyReferences element(s) that describe the item
tabindexShould be -1 (focus managed by menu container)
Best Practices
Always ensure exactly ONE item in a group has aria-checked="true"
Use role="group" with aria-label to create semantic radio groups
Provide visual indication of selected state (filled radio icon)
Update aria-checked immediately when selection changes
Use tabindex="-1" and manage focus via arrow keys
Support both Enter and Space key for selection
When menu opens, focus the currently selected item
Don't allow multiple items to have aria-checked="true" simultaneously
Don't use aria-checked="mixed" with menuitemradio (not valid)
Don't use for multi-select scenarios (use menuitemcheckbox instead)
Don't forget to provide visible focus indicators
menuitemradio vs menuitemcheckbox
menuitemradio
- Only one item can be selected per group
- No mixed state (true/false only)
- Example: Font size, Theme, Sort order
- Uses radio button-style visual (●)
- Mutually exclusive choices
menuitemcheckbox
- Multiple items can be selected
- Supports mixed state (true/false/mixed)
- Example: Bold, Italic, Show panels
- Uses checkbox-style visual (✓)
- Independent toggles
Common Use Cases
Font size, Font family, Line spacing selection
Light/Dark/System/High contrast modes
Name, Date, Size, Type sorting options
List/Grid/Compact/Detail view toggles
Application language preferences
Comfortable/Cozy/Compact display density
MM/DD/YYYY, DD/MM/YYYY, etc.
Local/UTC/Specific timezone selection
Accessibility Notes
Screen Reader Announcements
Screen readers announce menuitemradio elements as "radio menu item" followed by the label, position in group (e.g., "1 of 4"), and selection state. For example: "Medium, radio menu item, checked, 2 of 4". This helps users understand both the selection and the available options.
Selection Behavior
When a user selects a menuitemradio, you must update the aria-checked attribute of all items in the group: set the selected item to true and all others to false. This ensures screen readers correctly announce the new selection state.
Visual Distinction
Use a filled circle (●) for selected items and an empty circle (○) for unselected items. This radio-button style visual helps users distinguish menuitemradio from menuitemcheckbox, which should use checkmark (✓) style visuals.

