Accessibility
@carlos3g/element-dropdown ships sensible a11y defaults: every
touchable surface is exposed to screen readers, items announce
their selection state, the modal traps VoiceOver focus, and the
component honours the user's reduce-motion preference. Nothing
on this page requires any code change to benefit from — it's all
on by default.
Trigger
Both Dropdown and MultiSelect triggers expose:
accessibilityRole="combobox"— screen readers announce it as a dropdown.accessibilityState.expanded—truewhen the list is open.accessibilityState.disabled—truewhen thedisableprop is set.
Set accessibilityLabel on the component to give the trigger a
descriptive label, and accessibilityHint for an action
description:
<Dropdown
accessibilityLabel="Country selector"
accessibilityHint="Opens a list of countries"
// …
/>
The label is also propagated to the search input
(as {label} input) and the list (as {label} flatlist), so
tests and assistive tech can address each surface.
List items
Every list row exposes:
accessibilityRole="button"— TalkBack / VoiceOver announces rows as actionable.accessibilityState.selected—truewhen this item matches the current value.accessibilityState.disabled—truewhendisabledFieldis set and the item's value at that field is truthy.
By default, the row's accessibilityLabel is item[labelField].
To use a different field, set itemAccessibilityLabelField:
<Dropdown
data={data}
labelField="name"
valueField="id"
itemAccessibilityLabelField="fullDescription"
onChange={(item) => setValue(item.id)}
/>
Items are accessible regardless of whether you pass a
component-level accessibilityLabel. (Earlier upstream versions
silently set accessible={false} when the component label was
missing — that gate has been removed.)
Section headers
When you use the sections prop, the default section header
carries accessibilityRole="header" so VoiceOver / TalkBack's
"next heading" gesture surfaces group titles:
<Dropdown
sections={[
{ title: 'Berries', data: [/* … */] },
{ title: 'Citrus', data: [/* … */] },
]}
// …
/>
If you supply your own renderSectionHeader, set the role
yourself.
MultiSelect chips
Each chip in the selection row exposes:
accessibilityRole="button"accessibilityHint="Double tap to remove from selection"
The decorative ⓧ glyph is hidden from the accessibility tree
(accessibilityElementsHidden + importantForAccessibility="no-hide-descendants")
so screen readers don't read out "circled x" after the label.
The chip-row container is an
accessibilityLiveRegion="polite" region — toggling a row
announces the new chip without forcing the user to navigate back
to the row.
Modal isolation
The dropdown opens inside a React Native <Modal> whose root
view sets accessibilityViewIsModal. On iOS, this scopes
VoiceOver focus to the modal contents; users can't accidentally
swipe back into the screen behind the open list.
Reduce motion
Both components subscribe to the OS-level "Reduce Motion"
preference via AccessibilityInfo. When enabled, the modal
opens / closes with animationType="none" instead of the
default platform animation. The change happens live — toggling
the OS setting while a dropdown is open propagates without a
remount.
Tap target size
Use hitSlop to expand the trigger's tap
area without changing its visual layout. WCAG 2.5.5 recommends
at least 44 × 44 pt; if your trigger sits in tight UI, set
hitSlop to widen the touch target.
Font scaling
Labels and the search input respect the system font-scale setting
by default. Use allowFontScaling to
override per-component when you need to lock a fixed size for
layout reasons.
Search input
The built-in search input inherits an accessibility label
derived from the component's accessibilityLabel ({label} input). To set a more user-friendly label, override via
searchInputProps:
<Dropdown
search
accessibilityLabel="Country picker"
searchInputProps={{
accessibilityLabel: 'Search countries',
selectionColor: '#007AFF',
}}
// …
/>
searchInputProps is also where to set selectionColor,
returnKeyType, autoCapitalize, autoFocus, etc. for
keyboard / cursor a11y. See
Search input passthrough.
Known gaps
- Hardware keyboard navigation (arrow keys, Home/End, Escape,
typeahead) is not yet implemented. The component relies on
touch. Tracking upstream
#228as a follow-up; needs an active-descendant focus model that doesn't exist yet in the codebase. - Programmatic focus management — opening the dropdown does not auto-focus the search input, and closing does not return focus to the trigger. Adding this without device-level testing would risk regressions on Android, so it's deferred until we have a Fabric / web harness.
If you hit an accessibility bug or gap not listed here, open an
issue at
carlos3g/element-dropdown.