Sample panel
Delivery Live
v0.4.0 / 216 components / no build step
Paste a link tag and your page looks finished: buttons, forms, cards, charts. Mobile first, no build tools, and you ship only the parts you use.
npm i @barebase/style
Read the docs, or add the bare.style MCP server so your AI editor writes real class names.
Theme published
Both themes measured at AA.
Sample panel
Delivery Live
No script anywhere in this group. Every surface in it is a file linked in the head of this page, and the controls in it work.
01 / What it is
Every one of these is a thing the framework refuses to do rather than a feature it advertises.
This page loads no stylesheet of its own. Every rule you can see is bare.style styling bare.style, which is the fastest way to find out what a framework is missing.
02 / Size
Every number here comes from scripts/size.ts, which
compresses the real bytes. Run bun run size and check them.
All three are the delivered form: dist/, comments stripped,
gzip level 9 over one concatenated file. That is the only column comparable to another
framework's published figure, which is always its minified build.
| Bundle | Form | Files | Raw | gzip, one file | gzip, separate | brotli |
|---|---|---|---|---|---|---|
| Core only | delivered | 4 | 21,740 | 5,360 B | 5,669 | 4,023 |
| Core only | authored | 4 | 65,158 | 19,901 | 21,895 | 16,872 |
| Everything | delivered | 216 | 505,475 | 119,358 B | 136,975 | 50,421 |
| Everything | authored | 216 | 1,423,118 | 355,566 | 485,008 | 259,164 |
Two gzip columns because both are real delivery shapes. One file is a single link to a concatenated bundle. Separate is what per-element link tags over HTTP/2 cost, where each file is compressed on its own, and it is always the larger of the two.
03 / Agent cost
The styling lives in the stylesheet instead of in the class attribute, so there is far less markup to type. When the thing doing the typing is a language model billed by the token, less to type is less to pay.
Six fragments - a hero, a settings form, a table with a toolbar, a card grid, a modal and a row of dashboard tiles - written twice. Once in bare.style, once in the class lists Tailwind's own documentation produces for that component. The wording on the page is identical in both, so the only thing being compared is the styling.
| Counted as | Exact? | bare.style | Tailwind | Ratio |
|---|---|---|---|---|
| Characters | Exact | 5,796 | 11,992 | 2.1x |
| Token floor | Exact | 1,724 | 3,389 | 2.0x |
| Tokens | Estimated | 2,197 | 4,623 | 2.1x |
Counting exact tokens needs a 1.7 MB merge table from the model vendor, and shipping one inside a framework that argues against dependencies is not a trade worth making. So the last row is an estimate and says so. The middle row is not: it is the number of pieces the text splits into before any merging happens, which no real tokeniser can go below. All three land in the same place, which is the point of printing all three.
Run bun run token-cost to reproduce every figure above,
including the fragments themselves.
04 / Install
Copy src/ into the directory your site serves and link the
four core files plus whatever you use. Nothing to compile, nothing to configure, no
runtime.
Each file declares the shared layer order before its own rules, so these tags can go in any sequence. A component loaded later cannot outrank one loaded first by accident.
If you build with an AI editor, install the MCP server as well. It answers "how do I build a card" with the card, so the editor writes class names this framework actually ships instead of inventing them.
index.html
<!-- mandatory -->
<link rel="stylesheet" href="/css/core/reset.css">
<link rel="stylesheet" href="/css/core/tokens.css">
<link rel="stylesheet" href="/css/core/type.css">
<link rel="stylesheet" href="/css/core/base.css">
<!-- only what this page shows -->
<link rel="stylesheet" href="/css/layout/stack.css">
<link rel="stylesheet" href="/css/data/table.css">
<link rel="stylesheet" href="/css/forms/button.css">
05 / Rules
06 / Theme
Light is the default, and it is a default rather than a guess: a reader who has chosen nothing gets light, not whatever their operating system happens to ask for. Following the system is the third thing they can pick. A reader who has chosen keeps that choice even when the system says the opposite, and that is the part most implementations get wrong: source order alone does not achieve it, because a later media query still beats an equal-specificity class rule placed before it.
Two things make it airtight. The media query targets
:root:not(.theme-light), so a pinned light theme is never
matched rather than merely overridden afterwards. The pinned blocks are
:root.theme-light and
:root.theme-dark, which outrank the plain
:root default wherever they sit in the file.
The toggle in the bar above is currently pinned to light.
07 / Components
Every example in the documentation is a working component, and the markup shown under it is read out of that example rather than transcribed, so it cannot drift.