Performance, Tailwind v4, and more
Improved performance by 20-30% by working around useSyncExternalStore, new features, Black Friday discounts, and more.
Hi folks,
In recent months, we've focused on improving the performance of composite widgets like Combobox and Select. In the latest version, we increased the rendering speed of these components by 20-30% without any API changes. Additionally, we're working on new APIs and a built-in virtualization feature to enhance their performance even more.
useSyncExternalStore
In our measurements, we identified the performance bottleneck as the multiple uses of the useSyncExternalStore (uSES) React hook in composite item components. We even tested calling uSES with no-op stable subscribe and getSnapshot functions. However, React itself seems to perform extra work with each call to this hook compared to useState or useContext.
Each composite item component might call uSES over 10 times. When rendering more than 1,000 items, this could lead to thousands of hook calls. We improved this by consolidating them into a single uSES call.
Important: this doesn’t mean you should avoid using uSES in your components. It’s just that, in our case, we’ve identified it as the issue, and only when rendering hundreds of items.
offscreenBehavior
In addition to working on implementation details, we've added two new experimental props, offscreenBehavior and offscreenRoot, to the CollectionItem, CompositeItem, ComboboxItem, and SelectItem components.
The offscreenBehavior prop determines how the item component behaves when offscreen:
active (default): No changes occur. The component is fully rendered even when offscreen.
passive: The component is replaced by a super lightweight version when offscreen. Styles and content are rendered, eliminating the need to estimate size or absolutely position the items, while other React computations are skipped. The element is rendered to be accessible to assistive technologies.
lazy: Similar to passive, but once the component becomes active, it remains active even when offscreen. You can think of it like the loading="lazy" attribute on image elements.
Currently, these props are available only when importing components from the experimental @ariakit/react-core package:
import { SelectItem } from "@ariakit/react-core/select/select-item-offscreen";
<SelectItem
offscreenBehavior="lazy"
offscreenRoot={ref}
Unlike virtualization, offscreenBehavior requires no extra changes to component usage. It is designed to offer the best balance between developer experience and performance.
However, when rendering thousands of items, full virtualization is unmatched, particularly on low-end devices. That's why we are continually improving our experimental virtualization feature, which you can see in action with the select-combobox-virtualized example.
Interaction to Next Paint (INP) numbers
We tested rendering a Combobox component with 1,000 items on a MacBook Pro M1 with a 4x slowdown in production. Here are the results for different compound component libraries:
Opening popup (less is better):
Ariakit (virtualized) ≅ 56 ms
Headless UI (virtualized) ≅ 128 ms
Ariakit (offscreenBehavior) ≅ 168 ms
React Aria Components ≅ 368 ms
Ariakit ≅ 464 ms
Radix UI ≅ 568 ms
Headless UI ≅ 2920 ms
Closing popup (less is better):
Ariakit (virtualized) ≅ 24 ms
Headless UI (virtualized) ≅ 40 ms
Ariakit (offscreenBehavior) ≅ 48 ms
Ariakit ≅ 120 ms
Headless UI ≅ 128 ms
React Aria Components ≅ 160 ms
Radix UI ≅ 312 ms
Opening, navigating 10 items with arrow keys, then closing (less is better):
Ariakit (virtualized) ≅ 400 ms
Ariakit (offscreenBehavior) ≅ 536 ms
Headless UI (virtualized) ≅ 568 ms
Radix UI ≅ 880 ms
Ariakit ≅ 984 ms
React Aria Components ≅ 1808 ms
Headless UI ≅ 4088 ms
Tailwind v4 theme
We’re working on a Tailwind v4 theme with the following features:
Copy and paste
Beautiful, accessible, and fully customizable
Styles for buttons, dialogs, popover, form controls, and more
Autocomplete support similar to other Tailwind utilities
Use with any component library: Ariakit, Radix UI, Headless UI, or none at all
Use with any framework: React, Vue, Svelte, Solid, or none at all
This will be included in Ariakit Plus, so subscribing to Plus today will also grant you access to this product.
Stay tuned for upcoming announcements.
Ariakit Plus Black Friday 🎉
Ariakit Plus already has Black Friday discounts, which you can check out at ariakit.org/plus
With Plus, you gain access to a growing collection of exclusive examples, including all current and future ones. You'll also have access to the Tailwind v4 theme upon its release.
Ariakit Plus funds the open-source library and supports its ongoing improvement.
We are on Bluesky
A significant portion of the developer community is moving from Twitter/X to Bluesky, and we are too.
You can find us there:
We'll keep sharing work in progress and updates about Ariakit there. While we're not leaving Twitter entirely, expect updates to be prioritized on Bluesky.
Built with Ariakit
Highlight.io (via email)
If you've created something using Ariakit and want it featured in our next newsletter, just share your work on Bluesky mentioning @ariakit.org, or reply to this email.
More Ariakit
New releases: