Why
Why this library exists, what changed on the platform to make it possible, what it measures out to, and where it deliberately stops. If you only read one section, read when not to use it.
The problem
Accessible components have historically come in two broken shapes. Teams hand-roll a dialog or a combobox, miss the focus handling, the keyboard support, or the ARIA wiring, and ship something that looks fine and doesn't work for anyone using a keyboard or a screen reader. Or they install a library that gets it right and take tens of kilobytes of JavaScript for one widget, because the library was built at a time when the browser genuinely couldn't do that work itself.
Neither is a reasonable trade. This library is a bet that a third option now exists.
What changed
The platform caught up. <dialog> with showModal() gives focus containment, Escape to close, a top layer above the rest of the page, and an inert background, natively, and it is Baseline. <details>/<summary> is a real disclosure widget with correct implicit semantics, not a <div> and a click handler pretending to be one. @layer makes theming work without a specificity fight: a consumer's ordinary, unlayered CSS beats a library's layered component rules automatically, no !important needed.
Most of what an accessible-component library has historically carried is JavaScript reimplementing behavior the browser now ships for free. Two places that shows up directly in this library: accordion needs no JavaScript at all, because it's built on native <details>/<summary> rather than a scripted widget, and dialog/a11y is 580 B gzipped rather than the tens of kilobytes a scripted focus-trapping modal used to cost.
The measurements
Every number below comes from one run of one script, scripts/compare/measure.mjs in this site's repository. Each component is imported alone, together with the parts you actually need to render it (a Radix dialog is unusable without its trigger, portal and content), bundled with esbuild, minified, and gzipped. react and react-dom are external everywhere, so nothing in the table is carrying React's own weight. Figures are gzipped bytes.
The point of running them all through one script is that a byte in one row means the same as a byte in another. An earlier version of this page compared a tree-shaken abaabil figure against a whole-package figure quoted from elsewhere, which flattered abaabil by about four times. That is exactly the comparison this harness exists to prevent.
| Library | dialog | select | checkbox | radio | accordion | combobox | all |
|---|---|---|---|---|---|---|---|
| Headless and copy-in | |||||||
| abaabil | 580 | 530 | 534 | 444 | 364 | 1,097 | 2,422 |
| shadcn/ui | 25,522 | 42,657 | 16,407 | 20,891 | 19,437 | n/a | 51,864 |
| Radix | 13,493 | 30,223 | 5,772 | 10,227 | 8,655 | n/a | 36,745 |
| React Aria | 18,635 | 57,292 | 14,995 | 17,884 | 14,408 | 60,352 | 72,143 |
| Headless UI | 16,725 | 34,806 | 9,876 | 12,067 | 9,972 | 43,943 | 58,370 |
| Full component kits | |||||||
| MUI | 40,166 | 61,355 | 38,725 | 39,431 | 40,476 | 58,612 | 90,275 |
| Ant Design | 88,259 | 95,008 | 65,129 | 66,430 | 53,996 | 95,635 | 127,709 |
| Mantine | 26,595 | 44,901 | 16,348 | 16,103 | 13,660 | 44,181 | 59,576 |
| Chakra UI | 33,085 | 17,426 | 23,842 | 23,952 | 24,467 | 47,282 | 68,845 |
| Bootstrap | 9,994 | 1,184 | 2,255 | 2,255 | 6,169 | n/a | 13,154 |
The two groups are a real difference in what you are buying, not a ranking. The first lot ship behaviour and accessibility and leave the styling to you; the second bring an entire design language, so their numbers are the cost of a look as well as a control. They share one table because the useful question is what a dialog costs across all of them, and that should be one column you read down.
The all column is the fairest single number for the heavier libraries: it bundles every component that library offers into one graph, so a large shared runtime gets paid for once rather than once per row. Where a row reads n/a that library has no such component, and its all figure covers only the ones it does offer: Radix, shadcn and Bootstrap are five components there, everyone else is six. That column is also the one that reads worst for abaabil, and it is worth saying why. Every other library here shrinks dramatically when you take six components instead of one, because most of what you were measuring was shared infrastructure: React Aria Components needs only 20% more to go from its largest single component to all six, and Ant Design 34%. abaabil more than doubles, because there is almost no shared runtime to amortize. You are paying for six components' worth of code and nothing else, which is the whole design, but it does mean the gap narrows the more of the library you use. Against Ant Design, both measured over the same six components, it is 152x for a lone dialog and 53x for all six at once.
radix-ui, lucide-react, clsx and tailwind-merge. The figures above are its real published source, fetched from its registry, measured with everything it pulls in. That is why shadcn costs more than Radix in every row: it is Radix, plus the component, plus the utilities.bootstrap.min.css, which is 30,823 B gzipped as a single stylesheet you import whole.@mantine/core/styles.css at 39,617 B gzipped.The stylesheet nobody else is carrying
abaabil's figures above are JavaScript only, the same as every other column, but unlike Radix, React Aria Components, Headless UI and shadcn, abaabil arrives already styled. That cost is real, so it is tracked separately rather than folded in:
| Component | abaabil CSS |
|---|---|
| dialog | 371 B |
| select | 771 B |
| checkbox | 651 B |
| radio | 542 B |
| accordion | 528 B |
| combobox | 615 B |
| every component, one stylesheet | 2,692 B |
That last row is abaabil/styles.css, every component's styles in one file, for projects that would rather import once than per tier. For comparison, the two other libraries here that ship a stylesheet you import whole are Mantine at 39,617 B gzipped and Bootstrap at 30,823 B, so abaabil's entire stylesheet is about a fourteenth of Mantine's. There is deliberately no figure for shadcn/ui or for any Tailwind-based kit such as daisyUI: their CSS is generated from the classes your project happens to use, so there is no fixed number to quote, and quoting the unpurged file would be the same dishonest comparison this page exists to avoid.
What we deliberately do not do
Leaving things out is a design decision here, not an oversight, and each of the following is a specific trade, not a vague caveat:
- No focus trap.
showModal()already does it, and WCAG never required a scripted one. Shipping our own would be code that duplicates what the browser does natively, for no benefit. - No layout or spacing utilities. A labelled field and a bare button will not align in a flex row on their own. You choose the alignment. abaabil styles its own controls; it does not have an opinion about your layout.
- No theming runtime. Just CSS custom properties, set in
abaabil/theme.cssand overridable anywhere. No JS theme provider, no context, no runtime cost for restyling. - No
headingLevelprop on accordion. A heading nested inside<summary>is not reliably exposed to assistive technology (VoiceOver, for one, doesn't expose it as a heading), and<summary>has to be the literal first child of<details>, so wrapping it in a heading instead isn't an option either. Consumers who need reliable heading navigation across sections need a different, button-in-heading accordion pattern, which this component does not attempt to be. - The combobox is uncontrolled in 1.x. There is no
valueprop; you observe the selection throughonChangeinstead of driving it from external state. - CSS anchor positioning only as progressive enhancement. It is not Baseline, so the combobox listbox falls back to absolute positioning behind
@supportsrather than depending on it.
When not to use it
Naming what this is bad at is what makes the rest of this page credible, so here it is plainly:
- If you need date pickers, data tables, drag and drop, or internationalized widgets, React Aria Components does that and abaabil does not attempt to.
- If you want a component you paste into your codebase and own outright, source and all, shadcn's model fits that better than a package you install and import.
- If your app already has a component library that covers this ground, adding another one for eight controls is unlikely to be worth the churn.
abaabil is for the narrower case: a handful of common, genuinely accessible controls, at a size small enough that the size stops being a reason not to use them.
The name
Abaabil (أَبَابِيل) comes from Surah Al-Fil, chapter 105, verse 3 of the Qur'an. It's a plural with no singular form: it describes many separate, scattered groups, arriving from different directions. Classical narration holds that each bird in that verse carried three stones.
That happens to describe this library's own shape too: many small, independent components, each shipping three tiers. The architecture came first, for the reasons laid out earlier on this page. The name was picked afterward because it already fit, a pleasing coincidence rather than a design source.
وَأَرْسَلَ عَلَيْهِمْ طَيْرًا أَبَابِيلَ
Surah Al-Fil (105:3): "And He sent against them birds in flocks."