Accessibility States
| Severity | Serious |
|---|---|
| Accessibility Principle | Understandable |
| Affected users | Visual |
| Success criterion | 4.1.2 |
Accessibility states are attributes you add to a component to tell assistive technologies — such as screen readers (VoiceOver, TalkBack) and voice control software — about its current condition. They communicate things like whether a checkbox is ticked, a button is disabled, or a section is expanded.
Without these attributes, a screen reader user may see the correct label and role but have no way to know the element's current state.
aria-busy
• Severity: SeriousIndicates that an element is being updated and that assistive technologies should wait until the update is complete before announcing the change.[^1]
| Type | Default |
|---|---|
| boolean | false |
Expectations
- When: The user triggers (double tap) a component
- And: The component is performing a long (or async) task
- Then: The Screen Reader announces the component as busy
- And: The component is performing a long (or async) task
- When: The user pronounces the label while the component is busy
- Then: If the component is non-interactive while busy, Voice Control shows no interactive target — the command has no effect
Screen Reader behaviour
Assuming we have a button that adds the given product ID to the cart, which requires an API call:
const AddToCart = ({ productID }: { productID: string }) => {
const { addToCart, isAddingToCart } = useQuery(ADD_TO_CART);
const onPress = async () => {
const result = await addToCart();
};
return (
<Pressable
accessibilityLabel="Add to cart"
accessibilityRole="button"
ariaBusy={isAddingToCart}
onPress={isAddingToCart ? undefined : onPress}>
{isAddingToCart} ? <ActivityIndicator /> : <Text>Add to cart</Text>
</Pressable>
);
};
In the example, while the adding action is happening, the button:
- Ignores any press action
- Shows a loading spinner
While this works fine for sighted users, we must add the ariaBusy={isAddingToCart} property for visually impaired users to signal that the action is still happening.
The user double taps on the example component:
| VoiceOver | Talkback | |
|---|---|---|
| plays a sound as confirmation | Good |
The user focuses again on the component while the API is still in flight:
| VoiceOver | Talkback | |
|---|---|---|
| Add to cart, busy, button, double tap to activate | Good |
aria-checked
• Severity: SeriousIndicates the state of a checkable element. This field can either take a boolean or the "mixed" string to represent mixed checkboxes.
| Type | Default |
|---|---|
| boolean, 'mixed' | false |
Expectations
- When: The component can be toggle
- And: Receives the focus
- Then: The checked status is announced
- And: And the accessibility label is announced
- And: And the accessibility role is announced
- And: And the available action is announced
- And: Receives the focus
- When: The user pronounces the label
- Then: The Voice Control software recognizes the label
- And:
- The Voice Control software toggles the element — the checked state does not affect command recognition
- And:
- Then: The Voice Control software recognizes the label
aria-checked is not to be confused with aria-selected.
aria-checked is only meant to be used with checkboxes and toggle buttons.
Screen Reader behaviour
type ToggleButtonProps = {
checked: boolean;
label: string;
};
export const ToggleButton = ({ checked, label }: ToggleButtonProps) => {
return (
<Pressable
accessibilityLabel={label}
ariaChecked={checked}
ariaRole="button">
{label}
</Pressable>
);
};
Assuming the button label is: Add me to the list
The user selects the component
| State | VoiceOver | Talkback | |
|---|---|---|---|
| checked | ticked, Add me to the list, tickbox, double tap to toggle | Good | |
| not checked | not ticked, Add me to the list, tickbox, double tap to toggle | Good |
The user double taps on the example component:
| New state | VoiceOver | Talkback | |
|---|---|---|---|
| checked | ticked | Good | |
| not checked | not ticked | Good |
aria-disabled
• Severity: SeriousIndicates that the element is visible but disabled — it cannot be edited or interacted with.
| Type | Default |
|---|---|
| boolean | false |
Expectations
- When: The component is disabled
- And: Receives the focus
- Then: The Screen Reader announces its disabled status first
- And: The accessibility label is announced
- And: The accessibility role is announced
- And: Receives the focus
- When: The user pronounces the label
- Then: Disabled elements cannot be targeted by voice commands — Voice Control does not show an interactive overlay on disabled components
Screen Reader behaviour
const AddToCart = ({ disabled }) => {
return (
<Pressable
accessibilityLabel="Add to cart"
accessibilityRole="button"
disabled={disabled}>
Add to cart
</Pressable>
);
};
When the component receives the focus
| Is Disabled? | VoiceOver | Talkback | |
|---|---|---|---|
| false | Add me to the cart, button, double tap to activate | Add me to the cart, button, double tap to activate | Good |
| true | dimmed, Add me to the cart, button | disabled, Add me to the cart, button | Good |
aria-expanded
• Severity: SeriousIndicates whether an expandable element is currently expanded or collapsed.
| Type | Default |
|---|---|
| boolean | false |
Expectations
- When: The user triggers (double tap) a collapsable component, i.e. Accordion
- And: Its content is expanded/collapsed
- Then: The Screen Reader announces the component as expanded/collapsed
- And: Its content is expanded/collapsed
- When: The user pronounces the label
- Then: The Voice Control software recognizes the label
- And:
- The Voice Control software executes the action — the expanded or collapsed state does not affect command recognition
- And:
- Then: The Voice Control software recognizes the label
Screen Reader behaviour
export const Content = ({ content }) => {
const [isShowingMore, setIsShowingMore] = React.useState(false);
return (
<View>
<Text numberOfLines={isShowingMore ? undefined : 2}>{content}</Text>
<Pressable
accessibilityLabel="Show more"
accessibilityRole="button"
ariaExpanded={isShowingMore}
onPress={() => setIsShowingMore(showMore => !showMore)}>
{isShowingMore ? 'Show less' : 'Show more'}
</Pressable>
</View>
);
};
When the component receives the focus
| Is the content expanded? | VoiceOver | Talkback | |
|---|---|---|---|
| false | Show more, collapsed button, double tap to activate | Good | |
| true | Show less, expanded, button, double tap to activate | Good |
When the component is activated (double-tap)
| Is the new state expanded? | VoiceOver | Talkback | |
|---|---|---|---|
| false | Show more, collapsed button, double tap to activate | Good | |
| true | Show less, expanded, button, double tap to activate | Good |
aria-selected
Indicates whether a selectable element is currently selected or not.
| Type | Default |
|---|---|
| boolean | false |
Expectations
- When: The user selects an option component, i.e. radio button
- Then: The Screen Reader announces the component selected state first
- When: The user pronounces the label
- Then: The Voice Control software recognizes the label
- And:
- The Voice Control software selects the element — the selected state does not affect command recognition
- And:
- Then: The Voice Control software recognizes the label
Screen Reader behaviour
const OptionButton = ({ selected, label }) => {
return (
<Pressable
accessibilityLabel={label}
accessibilityRole="button"
ariaSelected={selected}>
<Text>{label}</Text>
</Pressable>
);
};
const TestScreen = () => {
const [selectedOption, setSelectedOption] = React.useState('Big');
return ['Big', 'Medium', 'Small'].map(size => {
return (
<Option
label={size}
selected={optionSelected}
onPress={() => setSelectedOption(size)}
/>
);
});
};
When the component receives the focus
| Is Selected? | VoiceOver | Talkback | |
|---|---|---|---|
| false | accessibility label, button, double tap to activate | Good | |
| true | Selected, accessibility label button, double tap to activate | Good |
When the component is activated (double-tap)
| Is the new state selected? | VoiceOver | Talkback | |
|---|---|---|---|
| true | accessibility label, selected | Good | |
| false | Good |
AMA dev runtime errors
NO_ACCESSIBILITY_STATE_SET
This error is raised when the UI state changes but assistive technologies are not informed — for example, a component's visual state updates without a corresponding accessibility state attribute.
This rule can be disabled by turning off the UI check in the ama.config.json file.
Best Practices
Use the right state for the right meaning
aria-checked and aria-selected are not interchangeable:
- Use
aria-checkedfor checkboxes and toggle buttons. - Use
aria-selectedfor items within a selection group — tabs, options in a list, radio-style buttons.
Using the wrong one causes screen readers to announce incorrect semantics to the user.
Keep state in sync with visual presentation
The state attribute must always reflect what the user sees. If a checkbox appears ticked, aria-checked must be true. Mismatches between visual and accessible state confuse all AT users and can fail automated accessibility audits.
Use aria-disabled to keep elements discoverable
A hidden or removed element cannot be found at all. Use aria-disabled when the element should remain visible and focusable for context, but not operable — for example, a "Submit" button that is disabled until a form is complete.
Announce aria-busy during async operations
Whenever a component triggers an async action and temporarily ignores interaction, set ariaBusy={true}. This prevents screen reader users from wondering why their action had no effect.