Skip to content

What oks-ui costs in bytes: every entry, and what 1.5 added

Published · Updated · Last reviewed · by Omkar Sahu

A line chart with a rising line on a dashboard card, standing for the size of each component measured in bytes.
ShareXLinkedIn
On this page
  1. Why I measured again
  2. How I measured
  3. What each entry costs
  4. A number that looked wrong
  5. What 1.5 added to real combinations
  6. Where the form growth came from
  7. A check I made on the way
  8. The script
  9. What I would take from it
  10. Limits
Every oks-ui entry bundled on its own and gzipped, then four realistic combinations on 1.4.1 and 1.5.0, with the files behind the biggest change. I ran everything on 25 October 2026 against the packages published on npm.

Why I measured again

My first bundle-size post compared two ways of importing a handful of components. Since then 1.5.0 added six components, a labels prop on every component that writes text, and a much larger validation module. The release notes say what was added. Nothing in them said what it cost, and a library that calls itself small should be able to say how small, entry by entry, and what each release changed.

So I measured it, in the way a user would: from the published packages, not from the source tree.

How I measured

I installed 1.2.2, 1.4.1 and 1.5.0 side by side (npm aliases make that one package.json) and, for every per-component entry such as oks-ui/button, built a one-line app that imports it. Each app is bundled with esbuild: minified, tree-shaken, browser ESM, with React left out because you ship it anyway. The output is gzipped at level 9. CSS is counted separately: the design tokens plus the entry's own stylesheet.

One thing to keep in mind when you read the numbers: an entry bundled alone carries every piece of shared code it uses, so the entries do not add up. I measure real combinations further down, where shared code is counted once.

Advertisement

What each entry costs

A bar chart in table form of the fourteen heaviest oks-ui 1.5.0 entries, JavaScript and stylesheet in kilobytes gzipped, with the smaller entries listed below it

Most entries are small. Button is 5.8 KB of JavaScript and 3.6 KB of CSS, 9.4 KB together, and the median entry is 4.4 KB. Four entries are heavy, and they are the ones you would expect: chart (100.5 KB), text-editor (91.3 KB), form-field-set (66.6 KB) and table (62.3 KB).

The new components are modest. color-picker is 15.7 KB, code-field 13.6 KB, media-library 5.7 KB, diff-view 4.6 KB, sortable 4.1 KB and drop-zone 3.8 KB. The translation provider, oks-ui/locale, is 0.5 KB.

A number that looked wrong

rating is 10.4 KB of JavaScript but 17.8 KB of CSS, which is more than the component itself. table and stepped-form show the same pattern. The cause is in the stylesheets, not the scripts: the CSS files of rating, table and stepped-form are 160, 167 and 161 KB before compression, and the form fields' own stylesheet is 157 KB. Each of them contains the styles of the text field and the select field (I counted the class names), although none of them renders one.

My reading of the build is that it follows imports between component folders and includes the whole stylesheet of each folder it reaches. Rating imports a form helper from FormFieldSet, and Table imports its checkbox from there, so both receive every form field's CSS. This is a real inefficiency and I have not fixed it. If you use Rating, Table or SteppedForm, you pay between 17.8 and 19.1 KB gzipped of CSS that is mostly form styling. It is the first thing I would change next.

What 1.5 added to real combinations

A table comparing gzipped JavaScript for four combinations of oks-ui components on version 1.4.1 and 1.5.0, with the change in bytes and percent

The combinations count shared code once, which is what your bundle really contains.

  • Button alone did not change at all: 5,775 bytes on 1.2.2, 5,771 on 1.4.1, 5,771 on 1.5.0.
  • Button + Modal + Toast grew by 265 bytes (2.0%). That is the cost of the shared labels code that each component now reads.
  • A six-component dashboard set (Button, Modal, Table, Toast, Tabs, Alert) grew by 1,829 bytes (3.8%).
  • Add FormFieldSet to Button and Modal and the growth is 7,415 bytes (17.6%), from 42,013 to 49,428.

The CSS barely moved. The design tokens went from 4,623 to 4,649 bytes gzipped, the utilities file stayed at 3,062, and across the 39 entries that exist in both versions the stylesheets grew by 425 bytes in total.

If you import every entry, the JavaScript grows by 27.8 KB, but eight of the 47 entries did not exist in 1.4.1, so that figure mostly measures the new components, not a regression.

Where the form growth came from

I wanted to know what the 7.4 KB was, so I bundled oks-ui/form-field-set from both versions with esbuild's metafile, which lists how many output bytes each package file contributes.

The package files in that bundle grew from 152.8 KB to 172.3 KB before compression, which is 19.5 KB. The form and validation code accounts for about half of it:

  1. In 1.4.1 one chunk of 9.0 KB held Form, the form context and the validation functions. In 1.5.0 the same code is two chunks, 13.1 KB for Form and the validation functions and 5.4 KB for the form context, 18.5 KB together. The bigger chunk now contains registerRule, oneOf, the schema functions and the async checks (validateFieldAsync, the asyncError message), and the 1.4.1 chunk contains none of those. That is about 9.5 KB more.
  2. The main form chunk, which holds the field components, grew by 2.8 KB. That is mostly the labels and the locale support added to the field components.

Those two add up to about 12.3 KB of the 19.5 KB. The rest, roughly 7 KB, is spread over smaller chunks that I did not pull apart, so I can tell you where most of the growth is, but not all of it. The validation rules, async checks and schema API are real features of that release, and FormFieldSet always includes them. There is no way today to leave them out. If you only need a required field and an email check, you still carry the rest.

A check I made on the way

Because every entry bundles shared code, I wanted to be sure the shared code really is shared, since a copy of the translation provider inside each entry would make LocaleProvider silently do nothing. I rendered LocaleProvider from oks-ui/locale around a Modal, an Alert and a Drawer, each imported from its own entry, on the server. All three showed the German text I had set, and none showed its English default. The entries import one shared chunk, so there is one context and one provider.

The script

This is the whole measuring script, so you can check my numbers and run it on your own set of components. Put it next to a project that has oks-ui and esbuild installed.

// node measure.mjs oks-ui/button oks-ui/modal
import { build } from "esbuild";
import { gzipSync } from "node:zlib";

const code = process.argv.slice(2).map((entry, i) => `import * as m${i} from "${entry}"; console.log(m${i});`).join("\n");
const { outputFiles } = await build({
  stdin: { contents: code, resolveDir: process.cwd(), loader: "js" },
  bundle: true, minify: true, write: false, format: "esm", platform: "browser",
  external: ["react", "react-dom", "react/jsx-runtime"],
  loader: { ".css": "empty" }, logLevel: "silent",
});
console.log(gzipSync(outputFiles[0].contents, { level: 9 }).length, "bytes gzipped");

Run on 25 October 2026, it prints 5,771 for oks-ui/button, and 13,792 for oks-ui/button oks-ui/modal oks-ui/toast on 1.5.0 against 13,527 on 1.4.1.

What I would take from it

  • Import each component from its own entry. Button alone stayed at 5.8 KB through three releases.
  • A form-heavy app pays for the validation features whether it uses them or not. That is the price of 1.5, and it is the number to watch in future releases.
  • Rating, Table and SteppedForm carry the form fields' stylesheet. Until that is fixed, expect between 17.8 and 19.1 KB of gzipped CSS from any of them.

Limits

These are gzip sizes from one bundler. A different bundler, brotli, or a CDN that compresses differently will give different numbers. They measure what is downloaded, not how long the scripts take to run. I counted shared code once in the combinations and in every entry in the first table, so neither table is a promise about your exact app. And I used the packages as published: if you build from source, or your bundler splits chunks differently, your totals will differ.

Advertisement

Omkar Sahu

I'm a front-end engineer based in Hyderabad, India, with more than 17 years of professional experience. I spent 12 years as a senior front-end developer, designing and building websites, and I have worked as a full-stack developer since 2022. I created oks-ui, a React component library with no runtime dependencies, and I write and maintain this site.

More about Omkar Sahu · All posts

Was this post helpful?

ShareXLinkedIn