Robin Malfait 9685af7148 Improve Listbox component performance (#3688)
This PR improves the performance of the `Listbox` component.

Before this PR, the `Listbox` component is built in a way where all the
state lives in the `Listbox` itself. If state changes, everything
re-renders and re-computes the necessary derived state.

However, if you have a 1000 options, then every time the active option
changes, all 1000 options have to re-render.

To solve this, we can move the state outside of the `Listbox` component,
and "subscribe" to state changes using the `useSlice` hook introduced in
https://github.com/tailwindlabs/headlessui/pull/3684.

This will allow us to subscribe to a slice of the state, and only
re-render if the computed slice actually changes.

If the active option changes, only 3 things will happen:

1. The `ListboxOptions` will re-render and have an updated
`aria-activedescendant`
2. The `ListboxOption` that _was_ active, will re-render and the
`data-focus` attribute wil be removed.
3. The `ListboxOption` that is now active, will re-render and the
`data-focus` attribute wil be added.

Another improvement is that in order to make sure that your arrow keys
go to the correct option, we need to sort the DOM nodes and make sure
that we go to the correct option when using arrow up and down. This
sorting was happening every time a new `ListboxOption` was registered.

Luckily, once an array is sorted, you don't have to do a lot, but you
still have to loop over `n` options which is not ideal.

This PR will now delay the sorting until all `ListboxOption`s are
registered.

On that note, we also batch the `RegisterOption` so we can perform a
single update instead of `n` updates. We use a microTask for the
batching (so if you only are registering a single option, you don't have
to wait compared to a `setTimeout` or a `requestAnimationFrame`).

## Test plan

1. All tests still pass
2. Tested this in the browser with a 2000 options. In the videos below
the only thing I'm doing is holding down the `ArrowDown` key.

Before:


https://github.com/user-attachments/assets/a2850c84-57f6-428a-aa51-e6f83d2aee97

After:


https://github.com/user-attachments/assets/157c6e99-5da8-4d72-87c6-a5e34f122531
2025-04-10 22:43:35 +02:00
2025-02-27 14:56:04 +01:00
2020-09-16 18:19:33 +02:00
2022-01-31 12:29:27 +01:00
2020-09-16 18:19:33 +02:00
2024-05-02 14:41:58 +02:00
2023-01-01 06:25:25 -05:00

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@latest
  • npm 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@insiders
  • npm 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 npm version npm downloads
@headlessui/vue npm version npm downloads
@headlessui/tailwindcss npm version npm downloads

Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

Discuss Headless UI on GitHub

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.

S
Description
Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
Readme 6.5 MiB
Languages
TypeScript 95.3%
Vue 4.3%
JavaScript 0.3%
Shell 0.1%