aria-selected
Indicates the current "selected" state of various widgets. Used for tabs, options in listboxes, grid cells, and tree items to communicate selection status to assistive technologies.
Overview
The aria-selected attribute indicates the current "selected" state of elements like tabs, options, grid cells, rows, and tree items. Screen readers announce this state to help users understand which items are currently selected.
This attribute can have three states: true (selected), false (not selected but selectable), or undefined (absence of attribute means not selectable).
Important Distinction
aria-selected="false" means "selectable but not currently selected". If an element is NOT selectable, omit the attribute entirely rather than setting it to false.
Live Demo: Selection Patterns
Tab List
This is the description panel content.
Tab 1: aria-selected="true"
Listbox
- Red
- Blue
- Green
- Yellow
- Purple
Selected: None
Calendar Grid
Selected date: None
Screen reader: For tabs: "Description, tab, selected, 1 of 3". For listbox: "Blue, selected, 2 of 5". For grid: "15, selected".
Attribute Values
trueThe element is currently selected. For tabs, this means the tab's panel is visible. For options, this means the option is the current selection.
falseThe element is selectable but not currently selected. This implies the element CAN be selected. Use this for other options in a listbox, non-active tabs, etc.
undefined(attribute absent)The element is NOT selectable. Only omit the attribute when selection is not a feature for that element. If all items in a list are potentially selectable, use false for unselected items.
Code Examples
Tab List
<!-- Basic aria-selected usage -->
<!-- Tab list with selected tab -->
<div role="tablist" aria-label="Product tabs">
<button
role="tab"
aria-selected="true"
aria-controls="panel-1"
id="tab-1"
>
Description
</button>
<button
role="tab"
aria-selected="false"
aria-controls="panel-2"
id="tab-2"
tabindex="-1"
>
Reviews
</button>
<button
role="tab"
aria-selected="false"
aria-controls="panel-3"
id="tab-3"
tabindex="-1"
>
Shipping
</button>
</div>
<!-- Tab panels -->
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
Description content...
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
Reviews content...
</div>Listbox Options
<!-- Listbox with selectable options -->
<label id="color-label">Choose a color:</label>
<ul
role="listbox"
aria-labelledby="color-label"
aria-activedescendant="option-blue"
tabindex="0"
>
<li
role="option"
id="option-red"
aria-selected="false"
>
Red
</li>
<li
role="option"
id="option-blue"
aria-selected="true"
>
Blue
</li>
<li
role="option"
id="option-green"
aria-selected="false"
>
Green
</li>
</ul>
<!-- Screen reader: "Blue, selected, 2 of 3" -->Grid Selection
<!-- Grid with selectable cells -->
<table role="grid" aria-label="Monthly calendar">
<thead>
<tr>
<th role="columnheader">Sun</th>
<th role="columnheader">Mon</th>
<!-- ... -->
</tr>
</thead>
<tbody>
<tr role="row">
<td role="gridcell" aria-selected="false" tabindex="-1">1</td>
<td role="gridcell" aria-selected="false" tabindex="-1">2</td>
<td role="gridcell" aria-selected="true" tabindex="0">3</td>
<!-- Selected date -->
<td role="gridcell" aria-selected="false" tabindex="-1">4</td>
</tr>
</tbody>
</table>
<!-- For range selection (calendar date range) -->
<td
role="gridcell"
aria-selected="true"
aria-label="March 15, start of selection"
>
15
</td>Tree Items
<!-- Tree with selectable items -->
<ul role="tree" aria-label="File browser">
<li
role="treeitem"
aria-selected="false"
aria-expanded="true"
>
Documents
<ul role="group">
<li role="treeitem" aria-selected="true">
report.pdf
</li>
<li role="treeitem" aria-selected="false">
notes.txt
</li>
</ul>
</li>
<li
role="treeitem"
aria-selected="false"
aria-expanded="false"
>
Images
</li>
</ul>
<!-- Note: aria-selected indicates selection for actions
like delete, move, copy - NOT expansion state -->React Components
// React Tabs with aria-selected
import { useState, useRef, useEffect } from 'react';
function Tabs({ tabs, defaultTab = 0 }) {
const [selectedIndex, setSelectedIndex] = useState(defaultTab);
const tabRefs = useRef([]);
const handleKeyDown = (e, index) => {
let newIndex = index;
switch (e.key) {
case 'ArrowRight':
newIndex = (index + 1) % tabs.length;
break;
case 'ArrowLeft':
newIndex = (index - 1 + tabs.length) % tabs.length;
break;
case 'Home':
newIndex = 0;
break;
case 'End':
newIndex = tabs.length - 1;
break;
default:
return;
}
e.preventDefault();
setSelectedIndex(newIndex);
tabRefs.current[newIndex]?.focus();
};
return (
<div className="tabs">
<div role="tablist" aria-label="Content sections">
{tabs.map((tab, index) => (
<button
key={tab.id}
ref={el => tabRefs.current[index] = el}
role="tab"
id={`tab-${tab.id}`}
aria-selected={index === selectedIndex}
aria-controls={`panel-${tab.id}`}
tabIndex={index === selectedIndex ? 0 : -1}
onClick={() => setSelectedIndex(index)}
onKeyDown={(e) => handleKeyDown(e, index)}
className={index === selectedIndex ? 'selected' : ''}
>
{tab.label}
</button>
))}
</div>
{tabs.map((tab, index) => (
<div
key={tab.id}
role="tabpanel"
id={`panel-${tab.id}`}
aria-labelledby={`tab-${tab.id}`}
hidden={index !== selectedIndex}
tabIndex={0}
>
{tab.content}
</div>
))}
</div>
);
}
// Selectable Listbox
function Listbox({ items, label, onChange }) {
const [selectedId, setSelectedId] = useState(null);
const listRef = useRef(null);
const handleSelect = (id) => {
setSelectedId(id);
onChange?.(id);
};
const handleKeyDown = (e) => {
const currentIndex = items.findIndex(i => i.id === selectedId);
let newIndex = currentIndex;
switch (e.key) {
case 'ArrowDown':
newIndex = Math.min(currentIndex + 1, items.length - 1);
break;
case 'ArrowUp':
newIndex = Math.max(currentIndex - 1, 0);
break;
case 'Home':
newIndex = 0;
break;
case 'End':
newIndex = items.length - 1;
break;
default:
return;
}
e.preventDefault();
handleSelect(items[newIndex].id);
};
return (
<div>
<label id="listbox-label">{label}</label>
<ul
ref={listRef}
role="listbox"
aria-labelledby="listbox-label"
aria-activedescendant={selectedId ? `option-${selectedId}` : undefined}
tabIndex={0}
onKeyDown={handleKeyDown}
>
{items.map(item => (
<li
key={item.id}
role="option"
id={`option-${item.id}`}
aria-selected={item.id === selectedId}
onClick={() => handleSelect(item.id)}
className={item.id === selectedId ? 'selected' : ''}
>
{item.label}
</li>
))}
</ul>
</div>
);
}Best Practices
For tabs, only one tab should have aria-selected="true" at a time
Set tabindex="-1" on non-selected tabs and tabindex="0" on selected tab
Provide clear visual indication of selected state
Use aria-selected with appropriate roles: tab, option, gridcell, row, treeitem
For multi-select, use with aria-multiselectable on the container
Don't use aria-selected on elements that cannot be selected
Don't confuse aria-selected with aria-current (for navigation)
Don't use aria-selected for checkboxes—use aria-checked instead
Don't forget to update tabindex when selection changes (for tabs)

