Fix VoiceOver bug for Listbox component in Chrome (#2824)
* fix VoiceOver bug for Listbox in Chrome Chrome currently has a bug if you use a `Listbox` with a `Label` and use the `aria-multiselectable` attribute. This combination will cause VoiceOver to _not_ announce the `role="option"` elements when interacting with them. If we drop the `aria-multiselectable` OR the `aria-labelledby` it starts working. Alternatively replacing `aria-labelledby` with `aria-label` won't work either. I filed a Chrome bug report about this here: https://bugs.chromium.org/p/chromium/issues/detail?id=1498261 --- Luckily there is a workaround in our `Listbox` implementation. Right now we always require the `Listbox.Button` to be there. The `Listbox.Options` component doesn't work on its own in our implementation. This means that whenever we open the `Listbox` that we have to go via the `Listbox.Button`. This `Listbox.Button` is already labelled by the `Listbox.Label` if there is one. This also means that we can safely drop the `id` of the label inside the `aria-labelledby` from the `Listbox.Options`. This wouldn't have worked if our `Listbox.Options` could be used in a standalone way without the `Listbox.Button`. At the end of the day the hierarchy looks like this: - Options is labelled by the Button - Button is labelled by the Label - Label Fixes: #2817 * update changelog
This commit is contained in:
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Allow changes to the `className` prop when the `<Transition />` component is currently not transitioning ([#2722](https://github.com/tailwindlabs/headlessui/pull/2722))
|
||||
- Export (internal-only) component interfaces for TypeScript compiler ([#2313](https://github.com/tailwindlabs/headlessui/pull/2313))
|
||||
- Fix infinite render-loop for `<Disclosure.Panel>` and `<Popover.Panel>` when `as={Fragment}` ([#2760](https://github.com/tailwindlabs/headlessui/pull/2760))
|
||||
- Fix VoiceOver bug for `Listbox` component in Chrome ([#2824](https://github.com/tailwindlabs/headlessui/pull/2824))
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
assertListboxButtonLinkedWithListbox,
|
||||
assertListboxButtonLinkedWithListboxLabel,
|
||||
assertListboxLabel,
|
||||
assertListboxLabelLinkedWithListbox,
|
||||
assertListboxOption,
|
||||
assertNoActiveListboxOption,
|
||||
assertNoSelectedListboxOption,
|
||||
@@ -515,7 +514,6 @@ describe('Rendering', () => {
|
||||
textContent: JSON.stringify({ open: true, disabled: false }),
|
||||
})
|
||||
assertListbox({ state: ListboxState.Visible })
|
||||
assertListboxLabelLinkedWithListbox()
|
||||
assertListboxButtonLinkedWithListboxLabel()
|
||||
})
|
||||
)
|
||||
|
||||
@@ -857,11 +857,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
}
|
||||
})
|
||||
|
||||
let labelledby = useComputed(
|
||||
() => data.labelRef.current?.id ?? data.buttonRef.current?.id,
|
||||
[data.labelRef.current, data.buttonRef.current]
|
||||
)
|
||||
|
||||
let labelledby = useComputed(() => data.buttonRef.current?.id, [data.buttonRef.current])
|
||||
let slot = useMemo<OptionsRenderPropArg>(
|
||||
() => ({ open: data.listboxState === ListboxStates.Open }),
|
||||
[data]
|
||||
|
||||
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Allow `<button>` to be in nested components in `<PopoverButton>` ([#2715](https://github.com/tailwindlabs/headlessui/pull/2715))
|
||||
- Don't overwrite user-defined template refs when rendering ([#2720](https://github.com/tailwindlabs/headlessui/pull/2720))
|
||||
- Fix missing `data-headlessui-state` attribute when `as="template"` ([#2787](https://github.com/tailwindlabs/headlessui/pull/2787))
|
||||
- Fix VoiceOver bug for `Listbox` component in Chrome ([#2824](https://github.com/tailwindlabs/headlessui/pull/2824))
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
assertListboxButtonLinkedWithListbox,
|
||||
assertListboxButtonLinkedWithListboxLabel,
|
||||
assertListboxLabel,
|
||||
assertListboxLabelLinkedWithListbox,
|
||||
assertListboxOption,
|
||||
assertNoActiveListboxOption,
|
||||
assertNoSelectedListboxOption,
|
||||
@@ -560,7 +559,6 @@ describe('Rendering', () => {
|
||||
textContent: JSON.stringify({ open: true, disabled: false }),
|
||||
})
|
||||
assertListbox({ state: ListboxState.Visible })
|
||||
assertListboxLabelLinkedWithListbox()
|
||||
assertListboxButtonLinkedWithListboxLabel()
|
||||
})
|
||||
)
|
||||
|
||||
@@ -663,7 +663,7 @@ export let ListboxOptions = defineComponent({
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-multiselectable': api.mode.value === ValueMode.Multi ? true : undefined,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-labelledby': dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id,
|
||||
onKeydown: handleKeyDown,
|
||||
|
||||
Reference in New Issue
Block a user