Skip to content

Commit 34f3074

Browse files
[Accessibility] Use id instead of children (#254)
Accessibility use id instead of children Co-authored-by: paco <[email protected]>
1 parent 07f1686 commit 34f3074

File tree

1 file changed

+19
-8
lines changed

1 file changed

+19
-8
lines changed

cmdk/src/index.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ type Context = {
137137
type State = {
138138
search: string
139139
value: string
140+
selectedItemId?: string
140141
filtered: { count: number; items: Map<string, number>; groups: Set<string> }
141142
}
142143
type Store = {
@@ -184,6 +185,8 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>((props, forwarded
184185
search: '',
185186
/** Currently selected item value. */
186187
value: props.value ?? props.defaultValue ?? '',
188+
/** Currently selected item id. */
189+
selectedItemId: undefined,
187190
filtered: {
188191
/** The count of all visible items. */
189192
count: 0,
@@ -251,6 +254,18 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>((props, forwarded
251254
sort()
252255
schedule(1, selectFirstItem)
253256
} else if (key === 'value') {
257+
// Force focus input or root so accessibility works
258+
if (document.activeElement.hasAttribute('cmdk-input') || document.activeElement.hasAttribute('cmdk-root')) {
259+
const input = document.getElementById(inputId)
260+
if (input) input.focus()
261+
else document.getElementById(listId)?.focus()
262+
}
263+
264+
schedule(7, () => {
265+
state.current.selectedItemId = getSelectedItem()?.id
266+
store.emit()
267+
})
268+
254269
// opts is a boolean referring to whether it should NOT be scrolled into view
255270
if (!opts) {
256271
// Scroll the selected item into view
@@ -785,16 +800,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, forwardedRe
785800
const isControlled = props.value != null
786801
const store = useStore()
787802
const search = useCmdk((state) => state.search)
788-
const value = useCmdk((state) => state.value)
803+
const selectedItemId = useCmdk((state) => state.selectedItemId)
789804
const context = useCommand()
790805

791-
const selectedItemId = React.useMemo(() => {
792-
const item = context.listInnerRef.current?.querySelector(
793-
`${ITEM_SELECTOR}[${VALUE_ATTR}="${encodeURIComponent(value)}"]`,
794-
)
795-
return item?.getAttribute('id')
796-
}, [])
797-
798806
React.useEffect(() => {
799807
if (props.value != null) {
800808
store.setState('search', props.value)
@@ -837,6 +845,7 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, forwardedRef) =
837845
const { children, label = 'Suggestions', ...etc } = props
838846
const ref = React.useRef<HTMLDivElement>(null)
839847
const height = React.useRef<HTMLDivElement>(null)
848+
const selectedItemId = useCmdk((state) => state.selectedItemId)
840849
const context = useCommand()
841850

842851
React.useEffect(() => {
@@ -864,6 +873,8 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, forwardedRef) =
864873
{...etc}
865874
cmdk-list=""
866875
role="listbox"
876+
tabIndex={-1}
877+
aria-activedescendant={selectedItemId}
867878
aria-label={label}
868879
id={context.listId}
869880
>

0 commit comments

Comments
 (0)