option
A selectable item in a listbox. Options represent individual choices that users can select within dropdown menus, comboboxes, and custom select components.
Overview
The option role defines a selectable item within a listbox. It represents one choice among multiple possibilities that a user can select. Options are fundamental building blocks for custom dropdown menus, comboboxes, and multi-select components.
Important: Elements with the option role must be contained within an element with role="listbox" or be an owned element of a listbox via aria-owns.
Native <option> vs role="option"
When possible, use native HTML <select> with <option> elements. They provide built-in keyboard navigation, screen reader support, and mobile accessibility. Use role="option" only when you need custom styling or functionality that native elements cannot provide.
Live Demo: Option Interactions
Single Select Listbox
Selected: apple
Multi-Select Listbox
Selected: react
Screen reader behavior: When navigating options, screen readers announce "[option name], selected" or "[option name], not selected" based on the aria-selected state.
Code Examples
Basic Listbox with Options
<!-- Basic listbox with option roles -->
<div role="listbox" aria-label="Choose a fruit">
<div role="option" id="opt1" aria-selected="true">
Apple
</div>
<div role="option" id="opt2" aria-selected="false">
Banana
</div>
<div role="option" id="opt3" aria-selected="false">
Cherry
</div>
</div>
<!-- Note: Always use aria-selected to indicate selection state -->Native HTML (Preferred)
<!-- Prefer native HTML when possible -->
<select aria-label="Choose a fruit">
<option value="apple" selected>Apple</option>
<option value="banana">Banana</option>
<option value="cherry">Cherry</option>
</select>
<!-- Native <option> elements automatically have
the correct semantics for screen readers -->Multi-Select Listbox
<!-- Multi-select listbox with multiple options -->
<div
role="listbox"
aria-label="Select frameworks"
aria-multiselectable="true"
>
<div role="option" aria-selected="true" id="react">
React
</div>
<div role="option" aria-selected="false" id="vue">
Vue
</div>
<div role="option" aria-selected="true" id="angular">
Angular
</div>
<div role="option" aria-selected="false" id="svelte">
Svelte
</div>
</div>Keyboard Navigation
<!-- Accessible listbox with keyboard navigation -->
<div
role="listbox"
aria-label="Select color"
aria-activedescendant="color-blue"
tabindex="0"
>
<div role="option" id="color-red" aria-selected="false">
Red
</div>
<div role="option" id="color-blue" aria-selected="true">
Blue
</div>
<div role="option" id="color-green" aria-selected="false">
Green
</div>
</div>
<script>
const listbox = document.querySelector('[role="listbox"]');
const options = listbox.querySelectorAll('[role="option"]');
let currentIndex = 1; // Blue is initially focused
listbox.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
currentIndex = Math.min(currentIndex + 1, options.length - 1);
updateFocus();
break;
case 'ArrowUp':
e.preventDefault();
currentIndex = Math.max(currentIndex - 1, 0);
updateFocus();
break;
case 'Enter':
case ' ':
e.preventDefault();
selectOption(options[currentIndex]);
break;
case 'Home':
e.preventDefault();
currentIndex = 0;
updateFocus();
break;
case 'End':
e.preventDefault();
currentIndex = options.length - 1;
updateFocus();
break;
}
});
function updateFocus() {
listbox.setAttribute('aria-activedescendant', options[currentIndex].id);
}
function selectOption(option) {
options.forEach(opt => opt.setAttribute('aria-selected', 'false'));
option.setAttribute('aria-selected', 'true');
}
</script>Disabled Options
<!-- Option with disabled state -->
<div role="listbox" aria-label="Choose plan">
<div role="option" aria-selected="false">
Free Plan
</div>
<div role="option" aria-selected="true">
Pro Plan
</div>
<div role="option" aria-selected="false" aria-disabled="true">
Enterprise Plan (Coming Soon)
</div>
</div>
<style>
[role="option"][aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
</style>React Component
// React Listbox Component with Options
import { useState, useRef, useEffect } from 'react';
function Listbox({ options, label, multiselect = false }) {
const [selected, setSelected] = useState(
multiselect ? [] : options[0]?.value
);
const [activeIndex, setActiveIndex] = useState(0);
const listboxRef = useRef(null);
const handleSelect = (value) => {
if (multiselect) {
setSelected(prev =>
prev.includes(value)
? prev.filter(v => v !== value)
: [...prev, value]
);
} else {
setSelected(value);
}
};
const handleKeyDown = (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex(i => Math.min(i + 1, options.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex(i => Math.max(i - 1, 0));
break;
case 'Enter':
case ' ':
e.preventDefault();
if (!options[activeIndex].disabled) {
handleSelect(options[activeIndex].value);
}
break;
case 'Home':
e.preventDefault();
setActiveIndex(0);
break;
case 'End':
e.preventDefault();
setActiveIndex(options.length - 1);
break;
}
};
const isSelected = (value) =>
multiselect ? selected.includes(value) : selected === value;
return (
<div
ref={listboxRef}
role="listbox"
aria-label={label}
aria-multiselectable={multiselect}
aria-activedescendant={`option-${activeIndex}`}
tabIndex={0}
onKeyDown={handleKeyDown}
className="listbox"
>
{options.map((option, index) => (
<div
key={option.value}
id={`option-${index}`}
role="option"
aria-selected={isSelected(option.value)}
aria-disabled={option.disabled}
onClick={() => !option.disabled && handleSelect(option.value)}
className={`option ${
isSelected(option.value) ? 'selected' : ''
} ${activeIndex === index ? 'focused' : ''}`}
>
{option.label}
</div>
))}
</div>
);
}
// Usage
<Listbox
label="Select your favorite framework"
multiselect={true}
options={[
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'angular', label: 'Angular' },
{ value: 'svelte', label: 'Svelte' },
]}
/>Combobox Pattern
<!-- Option within a combobox pattern -->
<label id="combo-label">Search country</label>
<div class="combobox-wrapper">
<input
type="text"
role="combobox"
aria-labelledby="combo-label"
aria-expanded="true"
aria-controls="country-listbox"
aria-activedescendant="country-usa"
/>
<ul
role="listbox"
id="country-listbox"
aria-labelledby="combo-label"
>
<li role="option" id="country-uk" aria-selected="false">
United Kingdom
</li>
<li role="option" id="country-usa" aria-selected="true">
United States
</li>
<li role="option" id="country-canada" aria-selected="false">
Canada
</li>
</ul>
</div>Keyboard Support
Keyboard interaction is managed by the parent listbox, not individual options. Here are the expected keyboard behaviors:
If focus is on the last option, wrapping behavior is optional.
If focus is on the first option, wrapping behavior is optional.
For multi-select, toggles the selection state without affecting other selected options.
Moves focus to the next option starting with the typed character(s).
Best Practices
Always set aria-selected to explicitly indicate selection state (true or false)
Ensure options are contained within a listbox or owned via aria-owns
Use native <select> and <option> elements when possible
Provide visual indication for focused and selected states
Use aria-multiselectable on listbox for multi-select scenarios
Implement aria-activedescendant on the listbox for keyboard navigation
Don't use option role outside of a listbox context
Don't forget to update aria-selected when selection changes
Don't make options focusable with tabindex - focus should be on the listbox
Don't use aria-checked instead of aria-selected (they have different semantics)
Supported ARIA Attributes
aria-selectedRequiredRequired. Indicates selection state (true/false)
aria-disabledIndicates the option is not selectable
aria-labelProvides accessible name if text content is insufficient
aria-labelledbyReferences element(s) that label the option
aria-describedbyReferences element(s) providing additional description
aria-setsizeTotal number of options in the list (for virtualized lists)
aria-posinsetPosition of the option in the set (for virtualized lists)
aria-checkedUsed in certain contexts like tree items (not typical for option)
Common Use Cases
Accessibility Notes
Focus Management
Individual options should not receive direct focus via tabindex. Instead, the parent listbox receives focus, and aria-activedescendant is used to indicate which option is currently highlighted. This allows screen readers to announce the focused option while maintaining a single tab stop.
Visual States
Provide clear visual distinction between: (1) focused option (the option keyboard users are currently on), (2) selected option(s), and (3) default/unselected state. Don't rely solely on color to indicate these statesβuse additional visual cues like checkmarks, borders, or background patterns.
Screen Reader Announcements
Screen readers announce options with their text content followed by selection state: "Apple, selected" or "Banana, not selected". For multi-select listboxes, they also announce "multi-selectable" when the listbox receives focus. Ensure option text is meaningful and distinguishable.
Mobile Accessibility
On touch devices, ensure options have adequate touch targets (at least 44Γ44 CSS pixels). Consider using native <select> elements for mobile as they provide native picker interfaces that are highly optimized for touch interaction.
Related Roles & Attributes
listboxRequired parent container for option elements
comboboxInput element that controls a listbox popup
groupGroups related options within a listbox
aria-selectedRequired attribute indicating selection state
aria-activedescendantIdentifies the focused option in a listbox
menuitemSimilar role for menu contexts (different semantics)

