This PR fixes a performance issue where all components using the `useIsTopLayer` hook will re-render when the hook changes. For context, the internal hook is used to know which component is the top most component. This is important in a situation like this: ``` <Dialog> <Menu /> </Dialog> ``` If the Menu inside the Dialog is open, it is considered the top most component. Clicking outside of the Menu or pressing escape should only close the Menu and not the Dialog. This behavior is similar to the native `#top-layer` you see when using native dialogs for example. The issue however is that the `useIsTopLayer` subscribes to an external store which is shared across all components. This means that when the store changes, all components using the hook will re-render. To make things worse, since we can't use these hooks unconditionally, they will all be subscribed to the store even if the Menu component(s) are not open. To solve this, we will use a new state machine and use the `useMachine` hook. This internally uses a `useSyncExternalStoreWithSelector` to subscribe to the store. This means that the component will only re-render if the state computed by the selector changes. This now means that at most 2 components will re-render when the store changes: 1. The component that _was_ in the top most position 2. The component that is going to be in the top most position Fixes: #3630 Closes: #3662 # Test plan Behavior before: notice how all Menu components re-render: https://github.com/user-attachments/assets/3172b632-0fa4-42db-970c-39efc827dd84 After this change, only the Menu that was opened / closed will re-render: https://github.com/user-attachments/assets/5d254bfc-5233-47a7-94d3-eb7a8593e14f
Headless UI
A set of completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
Documentation
For full documentation, visit headlessui.com.
Installing the latest version
You can install the latest version by using:
npm install @headlessui/react@latestnpm install @headlessui/vue@latest
Installing the insiders version
You can install the insiders version (which points to whatever the latest commit on the main branch is) by using:
npm install @headlessui/react@insidersnpm install @headlessui/vue@insiders
Note: The insiders build doesn't follow semver and therefore doesn't guarantee that the APIs will be the same once they are released.
Packages
| Name | Version | Downloads |
|---|---|---|
@headlessui/react |
||
@headlessui/vue |
||
@headlessui/tailwindcss |
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
Contributing
If you're interested in contributing to Headless UI, please read our contributing docs before submitting a pull request.