abaabil

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.

JavaScript weight per component in gzipped bytes, comparing abaabil with shadcn/ui, Radix, React Aria Components, Headless UI, Material UI, Ant Design, Mantine, Chakra UI and React Bootstrap. Libraries are rows; components are columns.
Librarydialogselectcheckboxradioaccordioncomboboxall
Headless and copy-in
abaabil5805305344443641,0972,422
shadcn/ui25,52242,65716,40720,89119,437n/a51,864
Radix13,49330,2235,77210,2278,655n/a36,745
React Aria18,63557,29214,99517,88414,40860,35272,143
Headless UI16,72534,8069,87612,0679,97243,94358,370
Full component kits
MUI40,16661,35538,72539,43140,47658,61290,275
Ant Design88,25995,00865,12966,43053,99695,635127,709
Mantine26,59544,90116,34816,10313,66044,18159,576
Chakra UI33,08517,42623,84223,95224,46747,28268,845
Bootstrap9,9941,1842,2552,2556,169n/a13,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.

shadcn/ui is not a dependency, so it is not measured like one. You copy its source into your own repository, where it imports 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.
React Bootstrap looks light here because its weight is not in JavaScript. Its select is a thin wrapper around the native element, so it measures at about 1 KB, but nothing on the page looks like Bootstrap until you also load bootstrap.min.css, which is 30,823 B gzipped as a single stylesheet you import whole.
Headless UI does not tree-shake per component. Its rows move less than the others because importing one widget pulls in a large shared core. That is a real property of the package, worth knowing before you reach for it, not a claim that its dialog contains that much dialog-specific code.
Material UI, Ant Design and Chakra UI put their styles in the JavaScript. Their figures already include the styling engine, so they are not missing a stylesheet column. Mantine is the exception in that group: its numbers above are JavaScript only, and it also ships @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:

abaabil CSS weight per component, gzipped
Componentabaabil CSS
dialog371 B
select771 B
checkbox651 B
radio542 B
accordion528 B
combobox615 B
every component, one stylesheet2,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.

These libraries are not all trying to do the same thing. React Aria Components covers far more surface than abaabil, including internationalization and complex data widgets this library does not attempt. Material UI and Ant Design bring an entire design system and dozens of components beyond these six. shadcn gives you source you own outright. A byte table cannot capture any of that, which is why it is spelled out here instead of left implied: these numbers tell you what a choice costs, not which choice is right.

Measured 2026-09-20 against abaabil 1.1.0, shadcn/ui new-york-v4 on radix-ui 1.6.7, @radix-ui/react-dialog 1.1.23, react-aria-components 1.21.1, @headlessui/react 2.2.10, @mui/material 9.4.0, antd 6.6.5, @mantine/core 9.6.1, @chakra-ui/react 3.37.0 and react-bootstrap 2.10.10, with esbuild 0.28.2 and React 19.3.0. Rerun with npm run measure in scripts/compare.

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.css and overridable anywhere. No JS theme provider, no context, no runtime cost for restyling.
  • No headingLevel prop 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 value prop; you observe the selection through onChange instead 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 @supports rather 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."