A tiny, mixin-first SCSS design system with one-file theme swap.
Bring your own components. Bring your own selectors. We bring the design system. No component library to fight, in React, Vue, Angular, Svelte, Web Components, Razor, SharePoint, or plain HTML — cia styles the markup you already own. One CSS file per theme — drop it in and the page restyles, no markup change. 24 themes. Zero runtime JavaScript — nothing in the package is loaded by a page. Six browser-native interactive components. Small enough to read in an afternoon.
Docs: cssisawesome.com · Install: npm install css-is-awesome
The recipes book: build any component in any framework using cia mixins — 32 recipes today, including
dialog,command-palette,combobox,combobox-multiselect,datepicker,data-table,pagination,breadcrumb,toast,file-upload,sortable-list,color-picker,app-shell,admin-dashboard-layout,auth-flow,otp-input,multi-step-wizard,confirm-dialog, five form-validation patterns (HTML5, react-hook-form, Zod, async, success-states), three i18n patterns,rtl-layout,print-to-pdf,print-spec,letterhead,mobile-navandbottom-nav. AI agents read recipes via MCP and generate components in your stack; humans read them at/docs/recipes.
Read llm.txt first. One file, the whole system: install path, hard rules, the mixin vocabulary, and the traps that make agents write wrong cia code. It ships in the npm package, so it's at node_modules/css-is-awesome/llm.txt in any project that has cia.
Then connect the MCP server and stop guessing at signatures. It answers from the real source — 35 tools covering themes, mixins, functions, tokens, recipes, components, theme validation, theme generation from design tokens, the token mapping itself, and a one-call status for version, counts and a surface hash.
{
"mcpServers": {
"css-is-awesome": {
"command": "npx",
"args": ["css-is-awesome-mcp"]
}
}
}That's the whole setup — css-is-awesome-mcp is a dedicated zero-install package, no manual dependency step. npx fetches and caches it on first run with no install command needed; if you'd rather pin an exact version in your own package.json/lockfile, npm install css-is-awesome-mcp works too — npx then uses the locally installed copy instead of fetching. (The in-repo copy at mcp/server.cjs still works too, if you'd rather not add a second package — see the MCP section below for that path, which needs npm install -D @modelcontextprotocol/sdk zod first since those are optional peers here.)
Why it matters more here than for older frameworks: no model has memorised cia's API the way it has memorised Tailwind's class names. Without llm.txt or MCP, an agent will confidently invent a Tailwind-shaped API. With them, it reads the real thing. Details at /docs/mcp.
Full breakdown in THREE-TIERS.md. cia is mixin-first since v0.8 — utility classes are an opt-in convenience.
npm install css-is-awesome
npm install -D sassOr let the wizard do the wiring — it detects your framework, picks a theme, writes the SCSS entry and connects the MCP server, in the project you're already in:
npm create cia@latest # add --dry-run to see the plan firstGuide: /docs/install/wizard.
@use 'css-is-awesome' as cia;
.checkout-cta { @include cia.btn(primary); }
.faq-item { @include cia.accordion; }
.modal { @include cia.modal; }<!-- One theme file: no data-theme attribute required -->
<link rel="stylesheet" href="/cia/themes/boilerplate/theme.css">Author your own class names; the mixin handles the styling. Mixins for buttons, forms, layout, typography, color, motion, plus the six zero-JS components: accordion, modal, tooltip, dropdown, tabs, copy-button — plus the mobile navigation family (hamburger, drawer, sheet, dock) and print-to-PDF via a pure-CSS @media print layer. Full reference at /docs/mixins.
Two imports, two jobs. Emit the tokens once from your root/global stylesheet (@use 'css-is-awesome'; or <link> a theme file), then import the zero-emit authoring barrel in each component stylesheet:
// Card.module.scss — emits no :root, safe under Next.js CSS Modules pure mode
@use 'css-is-awesome/api' as cia;
.product-card { @include cia.card-base($shadow: 2); }Next.js / Turbopack. The barrel works under Turbopack — the showcase app runs Next 16 on Turbopack for both
devandbuildand importscss-is-awesome/scss/apiin every stylesheet.Sass doesn't read package.json
exports, so addnode_modulestosassOptions.loadPaths.One reported hazard, stated with its provenance: a consumer whose own styles directory was on
loadPathshitTwo forwarded modules both define a mixin named stack. cia's internal forwards are all./-relative, and standard Sass resolves those against the importing file without consulting load paths —validate-packageasserts the barrel survives a hostilestyles/_mixins.scssahead ofnode_modules, and it does. So this appears to be a bundler-resolver deviation rather than Sass behaviour. If you hit it, drop your styles directory fromloadPaths.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/css-is-awesome@1/public/themes/boilerplate/theme.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/css-is-awesome@1/dist/css-is-awesome.min.css">Theme first (sets the tokens), library second. No data-theme attribute needed — a single theme file styles the page on its own. Swap the URL to swap the theme; the HTML never changes. Bundle tiers — dist/tokens.css (2.2 KB gz, :where(:root) vars only), dist/css-is-awesome.core.min.css (2.4 KB gz, tokens + resets), dist/css-is-awesome.min.css (8.0 KB gz, full).
@use 'css-is-awesome/scss/recipes/bare-tags';<button>Save</button>
<table>…</table>
<input type="email">One line styles the whole site. Zero classes. Wrapped in :where() (specificity 0,0,0), so any consumer selector — even another bare tag — wins automatically. No @layer, no cascade pollution.
One file per theme since v0.8. Each theme file emits :root, :root[data-theme="<name>"], so dropping one in restyles the page with no markup change — the data-theme attribute is optional for the single-file case. Set <html data-theme="..."> only when you load the all-in-one bundle (public/theme.css), where every theme shares one file and the attribute is the only thing telling them apart.
8 families, 24 files. Each family ships a dual-mode base (<name>, both light + dark via native light-dark()) plus single-mode <name>-light and <name>-dark variants for pairing.
| Theme | Mood | Modes |
|---|---|---|
| boilerplate | Neutral slate + clean blue, system fonts, drop-in starter | both |
| sketchbook | Warm washi paper / charcoal at night, sumi ink, indigo accent | both (brand default) |
| press | Editorial newsprint / night-edition, Playfair serif, press-red | both |
| prism | Vercel/Linear/Radix aesthetic, refined blue, neutral grays | both |
| cupertino | macOS AppKit, SF Pro, system blue, vibrancy blurs | both |
| glass | visionOS glassmorphism, iOS indigo, blur asymmetric per mode | both (Pattern C) |
| graphite | Brushed silver / machined dark aluminum, SF system stack | both |
| terminal | VT100 phosphor green, zero radii, CRT glow | dark-only base (terminal-light is the daylight companion) |
Pair two themes per mode with native <link media>:
<link rel="stylesheet" href="/themes/sketchbook-light/theme.css" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="/themes/terminal-dark/theme.css" media="(prefers-color-scheme: dark)">Newspaper by day, hacker terminal by night. No JS, no mixin — pure browser behavior. Most design systems give you dark mode; cia lets you ship a second brand at night. See /docs/themes/pairing.
Each theme is one file of CSS custom properties. Tokens only — no component rules. See public/themes/<name>/theme.css for the compiled output and scss/themes/<name>.scss for the sources. Full contract documented in THEMING.md.
The library's own default tokens emit under :where(:root) (specificity 0,0,0), so any theme declaration outranks them regardless of load order. That's :where(), not @layer — see the print section below for why cia refuses layers.
You can. Themes are open files. Edit any token, make brand-new themes, mix and match — cia treats themes as data, not internal magic.
# 1. Copy an existing theme as a starting point
cp scss/themes/boilerplate.scss scss/themes/midnight.scss
# 2. Edit tokens (see scss/themes/*.scss for the pattern + light-dark() usage)
# Wrap in @include cia.theme('midnight') { ... }
# 3. Build to public/themes/midnight/theme.css (also regenerates public/theme.css)
npm run build:css:themes
# 4. Validate against the token contract + a11y audit
node scripts/theme-validator.js public/themes/midnight/theme.css
# 5. Use it — just link the file. No data-theme attribute needed.
# <link rel="stylesheet" href="/themes/midnight/theme.css">A theme can also own the page behind your components: optional hero and band surfaces carry a background, an image or gradient, an ink colour and a scrim, applied with @include cia.surface(hero) or <body data-surface="hero">. Nothing is applied automatically, so adding them never moves a pixel until you ask.
Full authoring walkthrough: /docs/authoring/themes. The contract (127 required + 49 optional tokens) is at scripts/theme-contract.json.
Every theme declares the same slots: surfaces · ink · lines · primary · seal · accent · code · type · space · radius · shadow · blur · glow · motion. Components read tokens, themes set tokens, nothing else. 127 required, 49 optional.
Themes own the spacing scale. A theme declares the numbered scale --space-0 … --space-9 (contract-required), which is exactly what cia.space(4) compiles to — so a theme can ship tighter or airier rhythm without touching a component. The six t-shirt names (--space-2xs/xs/sm/md/lg/xl) are optional; the library emits them as references (--space-md: var(--space-4)), so they track the numbered scale automatically.
Component-shape tokens (--btn-radius, --card-radius, --input-radius, --modal-radius, --badge-radius, --tag-radius) are optional too, and cascade from the generic radii by default — --btn-radius: var(--radius-md, 0.25rem). Set the generic --radius-* scale to move everything; set a component token to make one thing an exception.
SVG pack — self-contained, drop-in. 49 glyphs vendored from Lucide ship at public/icons/core/<name>.svg. Nothing to install.
@use 'css-is-awesome/api' as cia;
.save-btn { @include cia.icon-svg(check); } // tinted via currentColor
.next { @include cia.icon-svg-text(arrow-right, $position: after); }Adding your own glyph is genuinely drop-in — no registration step. Put star.svg in public/icons/core/ and cia.icon-svg(star) works immediately; the name resolves straight to a filename. npm run validate-icons checks the 49 contract glyphs are present and does not object to extras.
Every icon emits a per-glyph custom property, so a theme can swap one without touching SCSS:
mask: var(--cia-icon-check, url('/icons/core/check.svg')) center / contain no-repeat;:root[data-theme='terminal'] { --cia-icon-check: url('/themes/terminal/icons/check.svg'); }Resolution order is per-theme override → core pack → 404.
Font Awesome — bring your own fonts. cia.icon-fa(), icon-fa-icon(), icon-fa-text() and icon-fa-spin() exist for teams already on FA. They map a name through $icon-fa-map (54 entries) to a codepoint and set the FA font family:
.a { font-family: "Font Awesome 6 Free"; font-weight: 900; content: "\f00c"; }fa-* mixins compile to valid CSS but render as tofu until you:
- supply the
.woff2files yourself, - put them where
$theme-fa-pathpoints (default/webfonts— this directory does not exist in the package), - call
@include cia.icon-fa-load;once at your root.
Missing font files don't error, so a silent tofu box is the failure mode. If you only want icons that work out of the box, use the SVG pack.
cia ships no component library — deliberately. Interactive patterns arrive as recipes: portable markdown files at scss/recipes/ that give you the correct HTML, the cia.X mixin calls to style it, and an a11y checklist graded against WCAG 2.2 AA. Copy the pattern into your own framework; you own the component, cia owns the styling and the accessibility homework.
Shipped: dialog, combobox, print-to-pdf, print-spec, letterhead, mobile-nav, bottom-nav. Queued next: datepicker, data-table, command-palette.
Layout doctrine: Grid is the skeleton, Flex is the quick moves. The page shell is CSS Grid with landmark-named areas (nav / main / footer — the map reads like the page); any control-dense region inside gets its own named-area grid whose gap carries all vertical rhythm; flex lives at the leaves for one-command flips (cia.flex($direction: column)). cia.page-layout() and cia.layout() own the maps — mobile is a different area map, never margin overrides.
Mobile navigation is system API, not an exercise for the reader. Four mixins — cia.hamburger, cia.drawer, cia.sheet, cia.dock — cover both phone layouts: the flex layout (one fluid nav/main/footer shell that reshapes with the screen, hamburger opening a slide-in drawer — recipe mobile-nav) and the app layout (a fixed bottom dock whose slots open slide-up sheets — recipe bottom-nav). Zero JavaScript: everything rides CSS Grid and the native Popover API — the browser owns open state, aria-expanded, Esc and light dismiss. The docs site dogfoods the app layout on phones. The same doctrine extends to dropdowns: on phones an interactive surface takes the space it's in, so a cia.dropdown menu opens 1px under its full-width trigger at the trigger's exact width — CSS anchor positioning, flipping above when the screen bottom would clip it. The full spec is the mobile playbook.
Humans read them at /docs/recipes; AI agents pull them over MCP with list_recipes / get_recipe.
The cia CLI converts another design system's config into a cia theme, so you start from your existing colors and spacing instead of a blank file:
npx cia migrate tailwind ./tailwind.config.js # auto-detects tailwind.config.* if omitted
npx cia migrate bootstrap ./scss/_variables.scssBoth accept --help for the full option list. Prose walkthroughs live at /docs/migration-tailwind and /docs/migration-bootstrap.
The CLI also carries the registry and the health check:
npx cia add --list # browse the recipe book
npx cia fix-theme t.css # move a theme onto current token names (--write to apply)
npx cia add bottom-nav # copy a recipe into your project — you own the pattern
npx cia analyze src/styles # design-system health: dead cia.* symbols, the
# space() scale trap, off-contract tokens (typos),
# off-scale lengths, hard-coded colors, BEM creep,
# missing focus-visible styling
npx cia theme from-tokens tokens.json --name acme --out src/styles/acme.css
# design tokens (DTCG v2025.10, Tokens Studio, or a
# flat --token map) → a complete, validated theme.css;
# missing required tokens inherit from a shipped base
npx cia theme map --path color.text.primary
# the path → token mapping from-tokens applies, as
# data: one path, or the whole table with --jsoncia analyze reads the real API surface from the installed package and exits non-zero on errors, so it slots straight into CI. It's deliberately low-noise about color: a hex used as a var(--token, #hex) fallback is token-driven (not flagged), and a literal inside @media print is an intentional paper colour (print escapes theme colours by design). Off-contract-token findings only fire on a near-miss of a real token — a typo like --inkk — never on your own custom tokens. Off-scale-length findings suggest the nearest named step (e.g. --radius-md) for a literal border-radius/padding/margin/gap value, as a hint toward using a token — never a claim about your active theme's exact pixel value, since themes are free to set their own numbers (Terminal sets every --radius-* to 0, deliberately). The default output is a graded report — a health score, a section per concern (Contract / Spacing / Color / Naming / Layout / API / Accessibility) with a ✓ when clean, and a suggested fix on each finding; add --verbose for the flat per-file list or --json for the machine shape. Full rule reference: /docs/analyzer.
Print support is a pure-CSS layer — the browser's native Print → Save as PDF is the generator, and cia ships no JavaScript for it:
@use 'css-is-awesome/api' as cia;
// Once, in a GLOBAL stylesheet — never inside a component module.
// It emits its own :root block plus @page, so don't wrap it in a selector.
@include cia.print-base; // optional flags: ($size, $margin, $freeze-animations, $link-urls, $link-origin, $page-numbers)
.site-nav { @include cia.print-hidden; } // drop chrome on paper
.print-note { @include cia.print-only; } // reveal paper-only content
.invoice { @include cia.print { border: 1px solid; } } // bare @media print wrapperprint-base also collapses animations to zero duration and pins them to their final frame, so a page snapshotted mid-entrance-fade doesn't print as invisible text. It deliberately does not force opacity: 1 or transform: none — that would fix the fade while flattening every intentional use of the same properties (a 0.15 watermark, a 0.4 disabled control, a stamp rotated -4deg). Elements that were never animating are left untouched. Read --is-print (0 on screen, 1 on paper) for custom effects.
Inside @media print, print-base always forces color-scheme: light, so paired light-dark() themes print their light branch for free. It also defines a print palette — --print-ink, --print-paper, --print-line, --print-muted (default ink-on-white with light grays) — and rebinds the theme's own colour tokens (--ink, --surface-*, --border-*, --code-*) onto it, so every theme prints as clean ink-on-paper and dark-only themes (Terminal) are legible with no flag. Three opt-in flags (all default off) take it further: $link-urls prints every link's destination via attr(href); $link-origin prepends an origin so internal /… links resolve to full URLs on paper; $page-numbers numbers the sheets in the @page footer. ($legible is now a deprecated no-op — the token rebind supersedes it; passing it warns.) The docs site and the theme editor at /themes turn those flags on to print themselves as paginated spec documents.
Those four --print-* tokens are ordinary custom properties, so print is themeable like any other surface. The default every theme inherits is clean black-on-white with light grays; a theme (or a single project) opts into its own paper identity only if it wants one — override the tokens and every element re-inks, no component touched:
// A letterhead: navy ink, silver rules. Put this in a theme's own @media print
// block, or ship it as a print-only stylesheet loaded with <link media="print">.
@media print {
:root {
--print-ink: #16233f; // deep navy
--print-muted: #5b6b86;
--print-line: #c7ccd6; // silver hairlines
// --print-paper stays #fff
}
}Loading it as a paired print sheet is the same mechanism as paired light/dark themes:
<link rel="stylesheet" href="/themes/letterhead/print.css" media="print">See it work. Two live demonstrations back the walkthroughs. The print-spec recipe paginates a page into a spec document (cover + table index, one page per section, honest sheet numbers). And /examples/print-to-pdf is a real invoice you Ctrl+P: on paper the site chrome and the button vanish, the invoice fills the whole sheet, its links print as full followable URLs ($link-urls + $link-origin), a sheet number appears, and a print-only QR code — generated at build time as inline SVG, zero browser JS, no image fetch — links the paper back to the live invoice-online page. Full walkthroughs: the print-to-pdf and print-spec recipes.
cia avoids !important everywhere else. The print layer is the exception, and it's deliberate.
@media contributes no specificity. print-hidden is included inside your selector, so the rule it generates has exactly that selector's specificity — and any later declaration at equal specificity wins, including in print. Measured in a browser:
| result in print | |
|---|---|
display: var(--print-hide) !important |
none ✓ hides |
same rule without !important |
flex ✗ prints anyway |
The competing display usually isn't yours — it's a utility class or a component library cia can never see. Without !important the mixin fails silently, on paper only, which is the worst place to discover it.
@layer would be worse, not better. Layered CSS always loses to unlayered CSS. If cia's print rules lived in a layer and your own CSS is unlayered (the normal case), your display: flex would win and the nav would print. cia can't require consumers to adopt layers — see .agent/decisions/decided/04-at-layer-decision.md. !important also inverts layer order, so the two don't compose the way you'd expect.
The scope is kept narrow: 8 !important declarations — seven inside @media print, and one inside @media screen (the half of print-only that hides the element on screen) — all doing one of two jobs — beating a display rule, or beating an author animation shorthand. The values stay variable-driven (--print-hide, --print-show), so you can still override behaviour without fighting the mixin.
cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol 2024-11-05) exposing 35 tools across 8 families (themes, mixins, functions, tokens · 127 required of them, animations, components, recipes, doc readers) plus assemble_prompt (context bundles) , resolve_size (snap design px values to cia's 4px grid) validate_theme (run the real theme validator on CSS you just wrote) theme_from_tokens (design-tokens JSON → a complete, validated theme.css) get_token_map (that mapping as data, or how one path resolves) and status (version, public-surface counts and a hash over every public name, in one ~40-token call). Any MCP-aware client (Claude Code, Cursor, Aider, Gemini, Copilot) can then query cia's real design system — mixin signatures, tokens, themes, recipes — instead of guessing, without grep-walking the repo. Full reference: /docs/mcp.
Recommended — zero install: use the dedicated css-is-awesome-mcp package. It depends on css-is-awesome and resolves your installed version's real source, so it's never out of sync — and the MCP SDK ships as a real dependency, not an optional peer you have to remember to add.
{
"mcpServers": {
"css-is-awesome": {
"command": "npx",
"args": ["css-is-awesome-mcp"]
}
}
}No install command required — npx fetches and caches the package the first time your MCP client runs it. Prefer a pinned version in your own lockfile instead? npm install css-is-awesome-mcp works the same way as any other dependency; npx then runs the locally installed copy rather than fetching one:
npm install css-is-awesome-mcpAlternative — the copy already in your node_modules: cia's own files manifest ships mcp/server.cjs too, for anyone who'd rather not add a second package. This copy needs its SDK peer deps installed manually first, since they're declared as optional peers (so a plain npm install css-is-awesome doesn't pull JS into a CSS-only install):
npm install -D @modelcontextprotocol/sdk zod{
"mcpServers": {
"css-is-awesome": {
"command": "node",
"args": ["node_modules/css-is-awesome/mcp/server.cjs"]
}
}
}The docs site is live at https://cssisawesome.com (production — Vercel, building main on every push), with a GitHub Pages mirror at https://jerry2d3d.github.io/css-is-awesome/ that deploys after each release. To run it locally:
git clone https://github.com/Jerry2d3d/css-is-awesome.git
cd css-is-awesome
npm install
npm run dev # http://localhost:5173The docs site is a Next.js 16 app at src/ that dogfoods the library — every page uses CSS Modules composed from the same tokens and mixins the library ships.
It also hosts the playground: write SCSS with cia mixins, see it render live against any of the 24 themes, and share the result as a link. Sass runs in your browser (dart-sass in a web worker against cia's own source), so nothing is uploaded and the site stays a static export. Every recipe page has a “Try in playground” button.
| Script | Does |
|---|---|
npm run dev |
Next.js docs site on port 5173 |
npm run build |
Static-exports docs site to out/ |
npm run build:css |
Compile library SCSS to dist/css-is-awesome.css |
npm run build:css:all |
Compile all bundles (full + core + utilities + minified) + themes + token types |
npm run build:css:themes |
Rebuild the 24 per-theme CSS files in public/themes/ and regenerate the all-in-one public/theme.css bundle |
npm run check:theme-drift |
Rebuild the themes into a scratch copy and fail if the committed artifacts don't match their SCSS sources |
npm run check:contract |
Diff the token contract against the last release tag and fail if the required/optional lists changed without the version bump VERSIONING.md requires |
npm run check:rtl |
Audit the SCSS for physical properties that should be logical (margin-left → margin-inline-start) |
npm run build:token-types |
Generate dist/tokens.d.ts from the contract |
npm run dtcg-to-scss |
Convert DTCG-format design tokens into cia SCSS |
npm run lint |
ESLint on the Next.js app |
npm run lint:scss |
Stylelint on the SCSS library |
npm run validate-themes |
Validate every theme against the 127-required-token contract + WCAG 2.2 AA contrast (FAIL-by-default since v0.7; checks both light-dark() branches and reports the worse) |
npm run validate-icons |
Validate the core icon pack against the 49-glyph contract |
npm run validate-api |
Assert the css-is-awesome/api barrel stays zero-emit |
npm run validate-package |
Pack + install into a temp project and compile every documented @use form — catches breakage that in-repo checks can't see |
npm run test:surfaces |
Assert cia never applies a page surface automatically — the guarantee that adding hero/band tokens changes nobody's existing page |
npm run test:tokens |
Exercise the design-tokens converter (DTCG / Tokens Studio / flat) against fixtures |
npm run verify:playground |
Compile real samples through the playground's in-browser Sass path in Node |
npm run pack:consumer |
Pack and install this build into a local consumer (defaults to ../boiler-project-ai); --dry-run supported |
npm test |
Playwright suite — axe a11y checks + per-theme visual snapshots |
Twelve checks, all gated in CI on every PR. Each one exists because the failure it catches actually happened.
| Check | What it proves |
|---|---|
lint / lint:scss |
ESLint on the site, stylelint on the library |
validate-themes |
every theme declares all 127 required contract tokens and meets WCAG 2.2 AA on 24 audited pairs — evaluating both light-dark() branches and keeping the worse result. Fails the build by default |
check:theme-drift |
the committed public/themes/** and public/theme.css artifacts still match their SCSS sources |
validate-icons |
the 49-glyph core pack is intact (extras allowed) |
validate-api |
the /api barrel still emits zero CSS until a mixin is called |
validate-package |
packs → installs into a temp project → compiles all 10 documented @use specifiers |
coverage:api |
calls 188/188 public mixins + functions and asserts the output |
coverage:mcp |
calls 35/35 MCP tools over stdio |
validate-recipes |
every recipe's SCSS compiles and its documented mixin calls resolve |
test |
Playwright — route smoke, axe a11y, per-theme visual snapshots, theme-editor behaviour, across three engines |
Call-and-assert coverage. SCSS has no line-coverage tooling, so cia measures whether every part of the public API is actually callable: parse every public @mixin/@function, generate a fixture that calls it, compile, and assert it works — no null leaking into CSS, functions return a value, mixins emit. 188/188 SCSS units and 34/34 MCP tools, with CI failing below 98%. A unit with no fixture counts as uncovered, so skipping a test lowers the number rather than hiding.
What 100% means here: every public mixin and function is invoked and produces sane output. It catches renames, broken signatures and undefined variables — it found one on its first run, an undefined $icon-size that broke four icon mixins. It does not prove the CSS is visually correct; that's a deliberate trade against golden-file snapshots, which would churn dozens of files on any token change. Page-level visual correctness is covered by the Playwright snapshots instead.
Size is gated, not just claimed. npm run size-budget fails CI when any bundle outgrows its budget, and names the docs that quote the number. Raising a budget is a deliberate edit in scripts/size-budget.mjs, in the same commit as the growth — the point isn't that the number never moves, it's that it never moves silently. (These figures had drifted to be overstated by up to 2× while nothing measured them.)
Known gaps, stated plainly: a11y runs on routes, not component states — axe checks a set of pages; individual component states are not swept. (Cross-engine coverage used to be the gap here; the suite now runs chromium, firefox and webkit, which matters because cia leans on light-dark(), :has(), [popover] and mask.)
Full detail: /docs/testing.
| Bundle | Size | Use case |
|---|---|---|
dist/tokens.css |
2.25 KB | Tokens only (:where(:root) CSS variables, no rules) — the purest mixin-first emit |
dist/css-is-awesome.core.min.css |
2.38 KB | Tokens + resets, no utilities or components |
dist/css-is-awesome.utilities.min.css |
4.75 KB | Every cia-* utility class, nothing else |
dist/css-is-awesome.min.css |
7.96 KB | Full bundle (everything) |
Per-theme themes/<name>/theme.css |
2.0–3.8 KB | One file per theme, both modes via light-dark(), drop-in with no markup change |
| Runtime JavaScript shipped in package | 0 KB | Nothing in the package is loaded by a page. The Node tooling (cia CLI, MCP server, validators) never reaches the browser; JS-driven UI features ship as separate add-on packages. |
Stable, published on npm (first published 2026-09-01). The mixin API, functions, token contract, and theme architecture are stable and under strict SemVer — breaking changes require a major bump. See VERSIONING.md for the policy.
The 1.0 surface is the v0.8 mixin-first reframe — twelve mixin renames, theme system collapsed to 8 single-file theme families, six zero-JS components, intrinsic-layout vocabulary, opt-in utilities — plus the recipes book, the Tailwind/Bootstrap migration on-ramp, print/PDF support, the in-browser Playground, the design-tokens on-ramp (npx cia theme from-tokens / theme map), and the 34-tool MCP server (now also available zero-install via the companion css-is-awesome-mcp package). The npm package ships ZERO runtime JavaScript by hard rule — nothing in it is loaded by a page; its Node tooling (CLI, MCP server, validators) never reaches the browser.
See CHANGELOG.md for the full history and MIGRATION.md for the v0.7 → v0.8 and v0.8 → v1.0 upgrade paths.
For the deep authoring reference (tier decisions, mixin contracts, agent rules), read AGENTS.md.
The site ships with a runtime feature-flag gate driven by public/flags.json:
{
"version": 1,
"comingSoon": false,
"comingSoonMessage": "...",
"announcement": { "active": false, "id": "", "status": "info", "message": "", "href": "" }
}Coming soon. Set "comingSoon": true and the site renders a full-page "we're building this" overlay on every route (the theme picker is still visible). Flip back to false to restore the normal docs experience. No rebuild required — the flag is fetched at runtime.
Announcements. Set announcement.active: true plus a unique announcement.id to show a dismissible banner on every route. Visitors can dismiss; the dismissal persists in sessionStorage keyed by id, so changing the id re-shows the banner.
Flag changes on a static host are effective after the next hard reload.
MIT. See LICENSE.