abaabil

How the sizes are measured

Every byte figure on this site is the gzipped size of a JavaScript bundle containing one component, imported with the parts it needs to actually render, with React treated as external. One script produces all of them in a single run, so a byte in one row means the same as a byte in another. This page is the method, the judgement calls inside it, and the things it does not tell you.

The procedure

In order, because it is one. Every library goes through the same five steps, including this one.

  1. Write an entry file that imports the component and nothing else, then references every binding so the bundler cannot remove what it was asked to weigh.
  2. Bundle it with esbuild: platform: browser, format: esm, process.env.NODE_ENV set to production.
  3. Mark react, react-dom and the JSX runtime external, so no column carries React's own weight.
  4. Minify.
  5. Gzip. The number reported is the gzipped byte count.

Nothing is read off a package listing, a size badge or a documentation page. Every library is measured on the same day from the same script, last on 21 September 2026.

Running it yourself

Each library's comparison page prints the exact import behind every figure. Put one in a file and this is the rest of it:

npx esbuild entry.jsx --bundle --minify --format=esm \
  --platform=browser \
  --external:react --external:react-dom --external:react/jsx-runtime \
  --define:process.env.NODE_ENV='"production"' \
  | gzip -c | wc -c

You should get the figure in the table, give or take a byte of gzip framing.

What a single component's figure is not

It is a first-component price, not a marginal one, and for every library here except this one those are very different numbers.

Take the row that prompted this page. React Bootstrap's popover measures 17,999 B. Most of that is overlay and positioning machinery the library pays for once: across the components it offers, React Bootstrap averages 1,410 B each, so the popover row is about 13 times what a popover actually costs someone already using the library. Read as “a popover costs 17,999 B” the figure is misleading. Read as “the first React Bootstrap overlay you add costs 17,999 B” it is right.

The gap between the two readings is the shared runtime, and it is the single most important thing to understand about the table. Below, each library's heaviest single component against the average cost of a component once you use the whole set.

Heaviest single component against per-component cost across the whole set, gzipped bytes.
Libraryheaviest oneall of themeachratio
abaabil1,1209,1292943.8x
Headless UI43,94368,0045,2318.4x
Radix30,22353,4932,97210.2x
Chakra UI47,282119,1414,10811.5x
Ant Design105,612243,2908,38912.6x
React Bootstrap18,02238,0681,41012.8x
shadcn/ui42,65788,0993,14613.6x
Mantine44,90193,4623,22313.9x
React Aria Components60,35495,0724,32114.0x
Material UI61,355125,4944,32714.2x

The ratio is how much a library's worst row overstates what one more component costs you. It runs from 3.8x here to 14.2x for Material UI, and the reason abaabil sits at the bottom is not a virtue of the measurement: it is that there is almost no shared runtime to spread. Every component brings its own few hundred bytes and that is the whole bill.

Which cuts the way you would expect. This library's advantage is largest when you use one component and narrows with every one you add. If you are going to use most of a design system, compare the “all of them” column and ignore the rest of the table. The full comparison leads with that column for the same reason.

Which parts count as the component

This is a judgement, not a measurement, and it is the largest single lever on any figure here. A Radix dialog imported as Root alone measures a few hundred bytes and renders nothing on screen. A dialog you can open needs Root, Trigger, Portal, Overlay, Content, Title and Close, which is 13,493 B.

An earlier version of this comparison used the first number. It put Radix at 3,422 B against abaabil's 674, a comparison that was wrong by about four times in this library's favour, and it was live. The fix made abaabil look worse, which is the only reason to trust the rest of this page.

So the part lists are published rather than described. Every comparison page prints the exact import for each of the thirty-two rows, next to abaabil's, and the whole set is in sources.json. If you think a part list is wrong, that is a specific, checkable disagreement, which is the point of publishing it.

What counts as having a component at all

Two rules, applied to every library including this one:

  • The library must ship a component for that purpose. A generic layout primitive is not a toolbar: Ant Design's Space, Mantine's and Chakra's Group, shadcn's ToggleGroup are not counted.
  • A generic input that accepts type="file" is not a file component. That excludes Material UI and React Bootstrap as well as shadcn.

The first pass applied neither consistently and credited four libraries with a toolbar none of them ships. The rules move the table against this library as often as for it: they shrink the rival totals, which is the headline figure, while widening the coverage gap, which is not.

What is left out, and who that favours

CSS. Every figure is JavaScript only. That is not a neutral omission. Radix and Headless UI ship no stylesheet at all, so their real cost to you is higher than their column shows, by however much CSS you end up writing. Ant Design, Material UI and Chakra carry their styling runtime inside the JavaScript, so for them it is counted. abaabil arrives styled and its stylesheet is measured separately rather than folded in, on the Why page.

React. External everywhere, because it is a peer dependency of all of these and counting it would add the same number to every column.

Your bundler. These are esbuild figures. Rollup and webpack shake trees slightly differently, so your own build may land a few percent either side. We have not cross-checked against them, and that is a real gap in this page rather than a detail.

Everything that is not size. Breadth, ecosystem, documentation, how long the edge cases have been in production. Every library here beats abaabil on at least one of those, which is what each comparison page says in its own words.

What is checked

A measurement nobody can break is a measurement nobody should believe. These run on every build.

  • A failed build is never data. The harness exits without writing if any bundle fails. It used to record {error: "..."} and the comparison table rendered eight cells reading [object Object], which shipped. A second check in the site build rejects any cell that is not a number or an explicit “no such component”.
  • The whole set cannot be smaller than its largest part. If it is, two imports collapsed into one and the run is invalid.
  • Every figure has a source. The build fails if a measured number has no recorded package, version, link and import.
  • It reproduces. Three consecutive runs of the harness returned byte-identical figures for all ten libraries.

Tree shaking, checked rather than assumed

Most of these libraries are imported from their package root. If a barrel export did not tree-shake, one component would be charged for the whole library, and the error would run in this library's favour. So it is measured: the same component, imported both ways.

The same component imported from the package root and by deep path, gzipped bytes.
Componentfrom the rootby deep path
Ant Design Modal88,25988,467
Material UI Dialog40,03940,166

The deep imports come out marginally larger. The barrels shake, and the large figures in the comparison are shared runtime those libraries genuinely carry, not an artefact of how they were imported.

The data

Three files, written by the same run, served as they are:

  • comparison.json — every figure, with the versions measured.
  • sources.json — the package, version, link and exact import behind each one.
  • delivered.json — abaabil's own per-entry-point figures, measured with Rollup to match its real publish pipeline, which is why they differ by a few bytes from the esbuild figures above.

If something here is wrong, it is wrong in public and in a form you can point at. That is the most this page can offer; the argument built on the numbers is a separate question from whether the numbers are right.