Headless UI
A set of completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
Documentation
For full documentation, visit headlessui.dev.
Packages
Name | Version | Downloads |
---|---|---|
@headlessui/react |
||
@headlessui/vue |
Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using the library:
Join the Tailwind CSS Discord Server
[Bug]: Uncaught RangeError: Maximum call stack size exceeded. focusElement of Modal
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
1.4.1
What browser are you using?
chrome
Reproduction repository
Not now.
Describe your issue
while we create
Modal
multiple (2+) it will throw meScreenshot here
BTW, can
Modal
need to provider flag props to disabledfocusElement
feature, in most case we don't need this, i think.Multi-Listbox
Hey there,
Just a first attempt/POC for a multi-select Listbox (see #181).
React-only at this stage.
https://user-images.githubusercontent.com/690667/123712986-af5edb00-d873-11eb-8085-10dbf11b9fc5.mov
It involves a few changes, mostly:
Listbox
state, to be able to detect selected options while the Listbox is openvalue
is an arrayAll in all, it seems to work ok, the only thing I can't get around is that when you use clickable items within
Listbox.button
(ex: badges with a remove action), the button gain focus on click, and it kind of messes things up.I've added a basic test case - just a start.
Let me know if you'd like me to explore / improve this further, or if you prefer to handle this yourself :)
HeadlessUI is not working with Nuxt 3
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
Describe your issue
It works fine with development but when I build nuxt (nuxi build) and start (yarn start) it gives error. I've tried to import headless ui component from @headlessui/vue/dist/index.js instead of @headlessui/vue but its still same.
[Bug]: Dialog: There are no focusable elements inside the
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
1.0.0
What browser are you using?
Chrome
Reproduction repository
https://codesandbox.io/s/gallant-butterfly-wnxjb
Description
When there is no focusable element inside modal, it fails with error
There are no focusable elements inside the <FocusTrap />
. I don't know if it's a feature or a bug, but there isn't said in the documentation that Dialog must include at least one focusable element and I totally see a use-case when there are modals without any focusable element. I think it at least should fail with warning instead of fatal error.Anyways I would like to thank you for the great job you all are doing with the tailwind ecosystem, we really appreciate your work!
[Bug]: Interacting with third-party components that use portals inside a Dialog closes the Dialog
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
1.0.0
What browser are you using?
Firefox
Reproduction repository
https://codesandbox.io/s/pensive-fast-yxokp?file=/src/App.vue
Describe your issue
When any component inside a Dialog renders itself elsewhere in the DOM using a Portal/Teleport, clicking in it closes the entire Dialog. The example above uses
vue-flatpickr-component
to create a date picker inside a modal, but because it renders the actual flatpickr instance somewhere else in the DOM, clicking any date closes the entire modal.The docs on this say:
But that isn't exactly the caseβit's not clicking the
DialogOverlay
specifically that closes the Dialog, but clicking anything that is not a child of the Dialog in the DOM.I believe the relevant part of the code is here: https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-vue/src/components/dialog/dialog.ts#L174-L184
What do you think about adding a way to disable that global
mousedown
handler, or adding an option to actually only close the Dialog based on clicks directly on theDialogOverlay
component?Transition not working in NextJs
It appears like Transition classes are not there initially on any div(
<Transition>
) Btw I'm using ( tailwindcss(^1.8.4) , styled-components & twin-macro)Sample code I'm trying to test on my NextJs WebApp
Regression: No transitions on open dialog [v1.6.5]
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
1.6.5
What browser are you using?
Chrome
Reproduction URL
https://headlessui.dev/vue/dialog
Describe your issue
No transitions take place when dialog initially appears or after closing/opening it again. Transition on close seems to work though. It used to work in v1.6.4.
[Bug]: Performance issues with Select Custom Dropdown on big lists
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
v1.1.1
What browser are you using?
Firefox
Reproduction repository
https://github.com/khuang7/headlessui-listbox-issue (yarn install then yarn start)
Describe your issue
I am trying to populate a pretty big list (500 elements) into the Simple Custom component found in TailwindUI for my project. The hover behavior when trying to select an option becomes quite slow when you try to move the mouse around on all the options. Also generating the list can sometimes be slow as well.
My workaround this problem is to replace Listbox.Options and Listbox.Option to ul and li html elements so that I can just call 'hover:' on the li elements which improves performance significantly. But I would prefer to keep using the HeadlessUI instead of HTML because I would lose a lot of the ARIA accessibility features.
I was wondering if there is a way to deal with the performance issues in the HeadlessUI. I could only pinpoint the issue being related to ListboxOptions and ListboxOption.
Feature: close popover manually
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
v1.0.0
What browser are you using?
Chrome
Reproduction repository
Describe your issue
At the moment it is not possible to manually close the popover. Let's say I want to build an editable table. The editable content should be shown in the popover. When I hit the save button it should also close the popover. This is not possible, because the slots does not expose the function togglePopover.
Dialog throws error if there are no focusable elements
I get the following error if I create a Dialog with no interactive components (just text):
Error: There are no focusable elements inside the <FocusTrap />
Control whether Menu closes on Menu.Item click
Hi there! I have a use case for the Menu component where I don't necessarily want the Menu to close when I click on a Menu.Item. Or, in other words, I would like to be able to control when the menu closes in some way.
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
v1.7.7
What browser are you using?
Chrome
Reproduction URL
https://codesandbox.io/s/suspicious-mountain-zroyy1?file=/pages/test.jsx
Describe your issue
The transition component is rendering strangely and there is a mismatch between the server and the client due to this.
When the options are
as={Fragment}
,show={true}
andappear=true
, in CSR (by clicking next/link in root page) it is working correctly merging child className and the transition props like enterFrom, enter, enterTo. But in SSR, the Transition component replaces the className of the child. In the reproduction URL I attached, the transition component replaced className="relative w-full h-full" with className="opacity-100 translate-x-0".Warning: Prop
className
did not match. Server: "transition duration-500 delay-[500ms] opacity-0 translate-x-5" Client: "relative w-full h-full".I went back to version 1.6.0 but the bug continued, and from 1.5.0 onwards, the component disappeared from the html tree (probably I did something wrong)
Focus is not trapped in `Dialog` when opened
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
v1.7.7
What browser are you using?
Chrome 108
Reproduction URL
https://stackblitz.com/edit/vitejs-vite-2fput4?file=src/App.tsx
Describe your issue
Focus is not trapped in
Dialog
when opened, it can be tabbed to any element else even the browser itself.The example is just exactly the same as the official one (copy and pasted). It's working in the official site but not in my local or reprod link
Combobox loses focus when option is selected with mouse click
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
v1.7.7
What browser are you using?
Chrome
Reproduction URL
https://user-images.githubusercontent.com/19432263/211062562-4c56cc35-8e83-4198-a8ff-97fed93f1845.mov
Describe your issue
Focus behaviour after an option is selected is different for keyboard and mouse interaction. I think when user selects option with mouse click the focus should stay on the input. At the moment the focus seems to go to the first tabbable element in the body.
There is also a discussion on this problem: https://github.com/tailwindlabs/headlessui/discussions/1984
Improve Combobox accessibility
Active WIP
This PR improves the Combobox because there are some differences compared to the Listbox that we didn't properly account for.
Big thanks to @afercia for providing us with a wonderful issue report that talks about the differences.
This commit fixes a few issues:
aria-selected
value was always based on theselected
value (which is correct for theListbox
) but it should be based on the visually selected value (in our case, this is theactive
value).aria-activedescendant
attribute from theComboboxOptions
, it should only exist on theComboboxInput
.aria-autocomplete
to theComboboxInput
, with a value oflist
so that assistive technology better understands what kind of Combobox we are dealing with.ComboboxInput
is made. Now we will trigger a change to the input when we open theCombobox
, we will set the value to''
and re-set to whatever value was present. We also make sure that the cursor position / selected range is reset so that the end user doesn't feel any differences (except for a better working VoiceOver on macOS).Some additional findings:
I also think that this is impossible to get right, so we have to chose the "best" way we think. Should we start announcing these changes in state manually instead of relying on on VoiceOver? π€
Some fun differences between macOS Safari + VoiceOver and macOS Chrome + VoiceOver:
So while using the
active
state foraria-selected
gives you at least consistent announcements, it also means that there is no way to differentiate betweenselected
andactive
.Single Value Mode
| Scenario | Implementation | Chrome Result | Expected? | Safari Result | Expected? | | :-------------------------------------------------------- | :------------------------------------------- | :-------------------------------------------------------------------------------------- | :-------: | :------------------------------------------------------------------------------------------------------------------ | :-------: | | Use
aria-selected
based on the visual selected value. |aria-selected: active ? true : undefined
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β | | Β |aria-selected: active
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β | | Usearia-selected
based on the actual selected value. |aria-selected: selected ? true : undefined
| Reads out every single value as "selected". | β | Reads out every single value as "selected", but stops announcing values listed after the actual selected value. | β | | Β |aria-selected: selected
| Reads out every single value as "not selected", except for the actual selected value. | β | Reads out every single value as "selected", but stops announcing values listed after the actual selected value. | β | | Usearia-checked
based on the visual selected value. |aria-checked: active ? true : undefined
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β | | Β |aria-checked: active
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β | | Usearia-checked
based on the actual selected value. |aria-checked: selected ? true : undefined
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β | | Β |aria-selected: selected
| Reads out every single value as "selected". | β | Reads out every single value as "selected". | β |Multi Value Mode
| Scenario | Implementation | Chrome Result | Expected? | Safari Result | Expected? | | :-------------------------------------------------------- | :------------------------------------------- | :--------------------------------------------------------------------------------------- | :-------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------: | | Use
aria-selected
based on the visual selected value. |aria-selected: active ? true : undefined
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β | | Β |aria-selected: active
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β | | Usearia-selected
based on the actual selected value. |aria-selected: selected ? true : undefined
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected", but stops announcing values listed after the first actual selected value. | β | | Β |aria-selected: selected
| Reads out every single value as "not selected", except for the actual selected values. | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected", but stops announcing values listed after the first actual selected value. | β | | Usearia-checked
based on the visual selected value. |aria-checked: active ? true : undefined
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β | | Β |aria-checked: active
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β | | Usearia-checked
based on the actual selected value. |aria-checked: selected ? true : undefined
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β | | Β |aria-selected: selected
| Reads out every single value as "selected". | β | Initially doesn't read out anything, but if you type something and undo that, then it reads out every single value as "selected". | β |Fixes: #2129 Co-authored-by: Andrea Fercia [email protected]
VoiceOver doesn't read the Combobox.Option (single value selection)
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
v1.7.1
What browser are you using?
Safari and VoiceOver (macOS)
Reproduction URL
Demo page https://headlessui.com/react/combobox or run the playground and go to http://localhost:3000/combobox/combobox-with-pure-tailwind
Describe your issue
Hello. Thank you for making headlessui components as accessible as possible, much appreciated.
I read some related issues / PRs and it seems to me there's a bit of misunderstanding about the usage of
aria-selected
on the combobox listbox. See for example https://github.com/tailwindlabs/headlessui/pull/1812, https://github.com/tailwindlabs/headlessui/issues/1599, and https://github.com/tailwindlabs/headlessui/pull/1243.Safari and VoiceOver (macOS) don't read the Combobox.Option because the
aria-selected
usage in a combobox listbox is supposed to be a bit different from the one in a standard listbox. It's a subtle difference but in all the comboboxes I've helped to build in the past it always turned out that other screen readers (NVDA, JAWS) are happy witharia-activedescendant
and do not care much aboutaria-selected
. Instead, VoiceOver refuses to read the options ifaria-selected
is omitted or used incorrectly.In the ARIA authoring practices, the example that is closest to the headlessui Combobox (single selection) is the Editable Combobox with List Autocomplete.
Quoting from the Listbox Popup Role, Property, State section of the W3C combobox example:
That is: in this pattern,
aria-selected
needs to be only set on the currently 'highlighted' option. It doesn't have anything to do with the 'selection' state, which is a concept that is extraneous to the W3C combobox pattern.This is also mentioned in the main page of the Combobox pattern: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/#wai-aria-roles-states-and-properties-6
This is different from the (stand alone) Listbox pattern, where
aria-selected
is indeed used for the selection state.In short: in the Combobox Listbox,
aria-selected
should be based on theactive
prop rather than theselected
prop. Of course, the multiselectable pattern needs to be handled differently as well as the stand-alone Listbox.Note: in https://github.com/tailwindlabs/headlessui/issues/1599#issuecomment-1235421018 it is mentioned that this woudl make VoiceOver announce every active option as
selected
. While slightly annoying, this is up to the screen reader implementation and it's in a way the expected behavior. Other screen readers opted to not announceselected
, for example NVDA doesn't, but again this is a vendor's choice. Regardless,aria-selected=true
needs to be set only on the highlighted option referenced by aria-activedescendant.To reproduce the bug:
W3C example: