css-is-awesome 1.19.2 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -5
- package/CHANGELOG.md +20 -0
- package/CONTRACT.md +10 -0
- package/MIGRATION.md +58 -0
- package/README.md +19 -11
- package/bin/analyze.cjs +47 -2
- package/bin/cia.cjs +10 -0
- package/bin/fix-theme.cjs +151 -0
- package/llm.txt +5 -5
- package/mcp/server.cjs +64 -3
- package/package.json +6 -3
- package/public/theme.css +138 -0
- package/public/themes/boilerplate/theme.css +6 -0
- package/public/themes/boilerplate-dark/theme.css +6 -0
- package/public/themes/boilerplate-light/theme.css +6 -0
- package/public/themes/cupertino/theme.css +6 -0
- package/public/themes/cupertino-dark/theme.css +6 -0
- package/public/themes/cupertino-light/theme.css +6 -0
- package/public/themes/glass/theme.css +6 -0
- package/public/themes/glass-dark/theme.css +6 -0
- package/public/themes/glass-light/theme.css +6 -0
- package/public/themes/graphite/theme.css +6 -0
- package/public/themes/graphite-dark/theme.css +6 -0
- package/public/themes/graphite-light/theme.css +6 -0
- package/public/themes/press/theme.css +6 -0
- package/public/themes/press-dark/theme.css +6 -0
- package/public/themes/press-light/theme.css +6 -0
- package/public/themes/prism/theme.css +6 -0
- package/public/themes/prism-dark/theme.css +6 -0
- package/public/themes/prism-light/theme.css +6 -0
- package/public/themes/sketchbook/theme.css +6 -0
- package/public/themes/sketchbook-dark/theme.css +6 -0
- package/public/themes/sketchbook-light/theme.css +6 -0
- package/public/themes/terminal/theme.css +6 -0
- package/public/themes/terminal-dark/theme.css +6 -0
- package/public/themes/terminal-light/theme.css +6 -0
- package/scripts/audit-pairs.json +171 -23
- package/scripts/fix-theme.cjs +184 -0
- package/scripts/theme-contract.json +33 -4
- package/scripts/theme-validator.js +40 -1
- package/scss/_layout.scss +129 -0
package/AGENTS.md
CHANGED
|
@@ -148,7 +148,7 @@ Authoring template (in your own project — a theme file is a global stylesheet,
|
|
|
148
148
|
|
|
149
149
|
`$standalone` defaults to `true` (emit `:root, :root[data-theme="<name>"]`). Pass `$standalone: false` only when your block is going into a multi-theme bundle where the bare `:root` would collide.
|
|
150
150
|
|
|
151
|
-
The validator (`node scripts/theme-validator.js`) enforces the token contract — **127 required +
|
|
151
|
+
The validator (`node scripts/theme-validator.js`) enforces the token contract — **127 required + 49 optional = 176 slots** — plus WCAG 2.2 AA contrast (**24 audited pairs per theme**, including five `--code-*` pairs). Themes that miss required tokens or fail contrast cannot ship without `--allow-a11y-fail`.
|
|
152
152
|
|
|
153
153
|
### Theming spacing (new — read this before you set a size token)
|
|
154
154
|
|
|
@@ -168,7 +168,7 @@ Why it matters: components call `cia.space(4)`, which resolves to `var(--space-4
|
|
|
168
168
|
|
|
169
169
|
Two `<link media>` themes still work under the new selector model: a stylesheet whose `media` doesn't match is loaded but never applied, so only the matching file's `:root` block lands.
|
|
170
170
|
|
|
171
|
-
Validator: `node scripts/theme-validator.js path/to/theme.css` (or `--all` for every shipped theme). Every theme must declare every required contract token (127 required in contract 1.
|
|
171
|
+
Validator: `node scripts/theme-validator.js path/to/theme.css` (or `--all` for every shipped theme). Every theme must declare every required contract token (127 required in contract 1.3; missing required tokens always fail, missing optional ones are reported as info). The audit also runs a WCAG 2.2 AA contrast check over 22 pairs; **a11y FAILs are fatal by default** as of v0.7. Pass `--allow-a11y-fail` to downgrade contrast failures to a report-only warning (the older `--strict` flag is accepted as a no-op alias). `--border-default` is treated as decorative per WCAG 2.2 SC 1.4.11 and reports as info, not FAIL.
|
|
172
172
|
|
|
173
173
|
### Theme init (Next.js / SSR consumers)
|
|
174
174
|
|
|
@@ -338,15 +338,16 @@ Or run this in-repo copy directly — needs its SDK peer deps installed manually
|
|
|
338
338
|
}
|
|
339
339
|
```
|
|
340
340
|
|
|
341
|
-
Either way it exposes **
|
|
341
|
+
Either way it exposes **34 tools** across 8 families:
|
|
342
342
|
|
|
343
343
|
- **Themes** — `list_themes`, `get_theme`, `search_themes`
|
|
344
344
|
- **Mixins** — `list_mixins`, `get_mixin`, `search_mixins` (real signatures — don't guess)
|
|
345
345
|
- **Functions** — `list_functions`, `get_function`, `search_functions`
|
|
346
|
-
- **Tokens** — `list_tokens`, `get_token`, `search_tokens` (127 required +
|
|
346
|
+
- **Tokens** — `list_tokens`, `get_token`, `search_tokens` (127 required + 49 optional contract tokens)
|
|
347
347
|
- **Animations** — `list_animations`, `get_animation`
|
|
348
348
|
- **Components** — `list_components`, `get_component`, `search_components`
|
|
349
349
|
- **Recipes** — `list_recipes`, `get_recipe`
|
|
350
|
+
- **Theme upgrades** — `fix_theme` (rewrite deprecated tokens to their replacements; returns text, never writes)
|
|
350
351
|
- **Doc readers** — `read_llm_txt`, `read_changelog`, `read_migration`, `read_theming`, `read_agents`, `read_contract`, `read_three_tiers`, `read_readme`, `read_versioning`
|
|
351
352
|
- **Helpers** — `assemble_prompt` (bundle context), `resolve_size` (snap a design px value to cia's 4px grid — call this whenever a design tool hands you a raw px value), `validate_theme` (run the real theme validator — contract + contrast — on a CSS string before you ship it), `theme_from_tokens` (DTCG / Tokens Studio / flat token JSON → a complete theme.css, base-inherited and validated; same function as `cia theme from-tokens`), `get_token_map` (that path → token mapping as data, or how one path resolves — read it instead of re-deriving the rules)
|
|
352
353
|
|
|
@@ -366,7 +367,12 @@ Either way it exposes **33 tools** across 8 families:
|
|
|
366
367
|
default boilerplate), unmapped paths pass through verbatim and are reported, a `--dark` file or paired
|
|
367
368
|
`color-light`/`color-dark` groups become `light-dark()`, and the validator + WCAG audit run before anything is
|
|
368
369
|
written. Same function as the MCP `theme_from_tokens` tool; `npx cia theme map [--json] [--path <token.path>]` prints the design-token → cia-token mapping that verb applies (same data as the MCP `get_token_map` tool), for anyone who needs to map one token name the way the converter would. Run any verb with `--help`. (`cia init` remains
|
|
369
|
-
planned.)
|
|
370
|
+
planned.) `npx cia fix-theme <theme.css> [--write]` moves a theme onto
|
|
371
|
+
current token names: it renames any DEPRECATED token to its replacement,
|
|
372
|
+
changing the property only — values, comments and ordering survive, so the
|
|
373
|
+
rendered theme is identical. Prints by default; writes only with `--write`;
|
|
374
|
+
a block already declaring the replacement is reported, never merged. Same
|
|
375
|
+
function as the MCP `fix_theme` tool.
|
|
370
376
|
- **JSON token export** — Tokens Studio-format sample in `figma-tokens/tokens.json`; it round-trips through `cia theme from-tokens` (paired light/dark groups → one `light-dark()` theme).
|
|
371
377
|
- **`llm.txt`** — at the repo root and served from the docs site; single-fetch
|
|
372
378
|
summary for any AI agent. Also readable over MCP via `read_llm_txt`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# [1.21.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.20.0...v1.21.0) (2026-09-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **cli:** cia fix-theme — rename deprecated tokens to their replacements ([665d871](https://github.com/Jerry2d3d/css-is-awesome/commit/665d87129f578d1ac38f410ce7461f37bbe24357))
|
|
9
|
+
|
|
10
|
+
# [1.20.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.19.2...v1.20.0) (2026-09-23)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **site:** correct the stale optional-token count on /docs/tokens ([c0d4db0](https://github.com/Jerry2d3d/css-is-awesome/commit/c0d4db047c858854cf54d34affc792e0c52f4427))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **site:** dogfood page surfaces on the home page; add them to the theme editor ([8dc3a65](https://github.com/Jerry2d3d/css-is-awesome/commit/8dc3a65778e28b57aeb67824062f26d7494e3f60))
|
|
21
|
+
* **themes:** page surfaces — hero and band, derived for every theme ([b984d86](https://github.com/Jerry2d3d/css-is-awesome/commit/b984d865e3463ae15e95083ff0697df0a109cf88))
|
|
22
|
+
|
|
3
23
|
## [1.19.2](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.19.1...v1.19.2) (2026-09-23)
|
|
4
24
|
|
|
5
25
|
|
package/CONTRACT.md
CHANGED
|
@@ -321,8 +321,11 @@ Every optional token belongs to exactly one **feature** in `scripts/theme-contra
|
|
|
321
321
|
| `surfaces-extended` | `--background-elevated`, `--background-hero`, `--background-overlay`, `--background-scrim` | Extra background layers (elevated, hero, overlay, scrim) for themes that want more than the required surface set; each falls back to a required surface. |
|
|
322
322
|
| `borders-extended` | `--border-card`, `--border-divider`, `--border-focus-ring`, `--border-input` | Per-context border colours (card, divider, focus ring, input); each falls back to --border-default / --border-focus. |
|
|
323
323
|
| `logo` | `--logo-default`, `--logo-mark`, `--logo-monochrome`, `--logo-wordmark` | Theme-aware logo assets (default, mark, monochrome, wordmark) as url() or SVG data values for the icon/brand mixins. |
|
|
324
|
+
| `page-surfaces` | `--page-hero-bg`, `--page-hero-image`, `--page-hero-ink`, `--page-hero-scrim`, `--page-band-bg`, `--page-band-image`, `--page-band-ink`, `--page-band-scrim`, `--background-hero` *(deprecated)* | Two page-level surfaces a theme owns outright: a **hero** for a landing page and a **band** for section stripes and footers. Each carries a background colour, an optional gradient or `url()` image, an ink colour, and a scrim that keeps text legible over an image. **Declaring them changes nothing on its own** - a page only picks a surface up through `cia.surface(hero|band)` or `data-surface`, which is why every shipped theme defines them without moving a pixel on an existing site. Contrast over an *image* cannot be measured, so the scrim carries that weight: the mixin applies 50% black whenever an image is passed to it. |
|
|
324
325
|
| `touch-target` | `--touch-target-min` | Minimum interactive target size read by form and button mixins (WCAG 2.2 SC 2.5.8); the library default is 24px. |
|
|
325
326
|
|
|
327
|
+
> **Deprecated - `--background-hero`.** It sat in the contract unread: no mixin consumed it, the editor could not set it, and nothing documented it. `--page-hero-bg` replaces it and **falls back through it**, so an existing declaration keeps working untouched. It stays valid until contract 2, per the lifecycle in [`VERSIONING.md`](./VERSIONING.md).
|
|
328
|
+
|
|
326
329
|
---
|
|
327
330
|
|
|
328
331
|
## Print (optional)
|
|
@@ -632,3 +635,10 @@ A PR that adds a new contract glyph must:
|
|
|
632
635
|
Per-theme override glyphs are NEVER required by the contract — themes
|
|
633
636
|
opt in glyph-by-glyph by declaring `--cia-icon-<name>` and shipping the
|
|
634
637
|
replacement file alongside.
|
|
638
|
+
>
|
|
639
|
+
> **Deprecations are machine-readable.** Every one lives in the `deprecated` map in
|
|
640
|
+
> [`scripts/theme-contract.json`](./scripts/theme-contract.json) with the token it replaces,
|
|
641
|
+
> when it was deprecated and when it goes. The validator prints it, `get_token` returns it,
|
|
642
|
+
> `npx cia analyze` flags it in your stylesheets, and `npx cia fix-theme <file> --write`
|
|
643
|
+
> renames it for you — property only, so the theme renders identically. `check:contract`
|
|
644
|
+
> fails the build if a deprecation points at a token that does not exist.
|
package/MIGRATION.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
Breaking changes between css-is-awesome versions, and how to migrate.
|
|
4
4
|
|
|
5
|
+
## Page surfaces — contract 1.3 (no action required)
|
|
6
|
+
|
|
7
|
+
**Nothing changes on your site when you upgrade.** Two new optional surfaces
|
|
8
|
+
exist — a `hero` for a landing page and a `band` for section stripes — and
|
|
9
|
+
every shipped theme now declares a background and an ink colour for both. That
|
|
10
|
+
sounds like a visual change and is not one, for a single reason: **cia never
|
|
11
|
+
applies a page surface on its own.**
|
|
12
|
+
|
|
13
|
+
A page takes a surface only when you ask for it:
|
|
14
|
+
|
|
15
|
+
```scss
|
|
16
|
+
.landing { @include cia.surface(hero); } // or <body data-surface="hero">
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Until then the tokens sit unread, exactly as `--background-hero` has since it
|
|
20
|
+
was added. `scripts/test-no-auto-surface.mjs` compiles every shipped bundle and
|
|
21
|
+
fails if any rule reads a page-surface token or emits a `[data-surface]` rule,
|
|
22
|
+
so the guarantee is enforced rather than promised.
|
|
23
|
+
|
|
24
|
+
### What changed at a glance
|
|
25
|
+
|
|
26
|
+
| Area | Before | After |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Contract version | `1.2` | **`1.3`** — eight optional tokens, one new `page-surfaces` feature |
|
|
29
|
+
| Required tokens | 127 | 127, unchanged |
|
|
30
|
+
| `--background-hero` | optional, read by nothing | **deprecated**; `--page-hero-bg` falls back through it, so an existing declaration keeps working until contract 2 |
|
|
31
|
+
| Your custom theme | validates | validates — the new tokens are optional, and missing optional tokens report as info |
|
|
32
|
+
|
|
33
|
+
Nothing here needs doing. If you would rather move onto the current name now,
|
|
34
|
+
`npx cia fix-theme <your-theme.css>` shows what it would change and
|
|
35
|
+
`--write` applies it. It renames the property and nothing else, so the theme
|
|
36
|
+
renders identically; a block already declaring `--page-hero-bg` is reported
|
|
37
|
+
rather than merged.
|
|
38
|
+
|
|
39
|
+
### If you want a hero
|
|
40
|
+
|
|
41
|
+
Declare the tokens your theme needs and apply the surface where you want it:
|
|
42
|
+
|
|
43
|
+
```css
|
|
44
|
+
:root[data-theme="brand"] {
|
|
45
|
+
--page-hero-bg: light-dark(#f2f6fe, #121d36);
|
|
46
|
+
--page-hero-ink: light-dark(#09090b, #fafafa);
|
|
47
|
+
/* optional */
|
|
48
|
+
--page-hero-image: url("/hero.jpg");
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
An image is a `url()` you host or a gradient, because a theme is one CSS file.
|
|
53
|
+
Contrast over a photo cannot be measured, so pass the image through the mixin
|
|
54
|
+
(`cia.surface(hero, $image: url("/hero.jpg"))`) and it lays 50% black between
|
|
55
|
+
the picture and your text. A colour-only surface gets no scrim — a wash over a
|
|
56
|
+
flat colour would only darken it.
|
|
57
|
+
|
|
58
|
+
### If you declared `--background-hero`
|
|
59
|
+
|
|
60
|
+
Nothing to do. It still resolves. Rename it to `--page-hero-bg` whenever it
|
|
61
|
+
suits you; the tooling will flag it as deprecated in the meantime.
|
|
62
|
+
|
|
5
63
|
## v1.0 — mixin-first goes stable (published as 1.1.0)
|
|
6
64
|
|
|
7
65
|
**There are no breaking changes between v0.8.1/0.8.2 and v1.0.0.** 1.0.0
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
**Read [`llm.txt`](./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.
|
|
16
16
|
|
|
17
|
-
**Then connect the MCP server** and stop guessing at signatures. It answers from the real source —
|
|
17
|
+
**Then connect the MCP server** and stop guessing at signatures. It answers from the real source — 34 tools covering themes, mixins, functions, tokens, recipes, components, theme validation, theme generation from design tokens and the token mapping itself.
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
@@ -79,7 +79,7 @@ Author your own class names; the mixin handles the styling. Mixins for buttons,
|
|
|
79
79
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/css-is-awesome@1/dist/css-is-awesome.min.css">
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
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` (
|
|
82
|
+
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).
|
|
83
83
|
|
|
84
84
|
### 3. Bare tags (opt-in Pico-mode)
|
|
85
85
|
|
|
@@ -145,11 +145,13 @@ node scripts/theme-validator.js public/themes/midnight/theme.css
|
|
|
145
145
|
# <link rel="stylesheet" href="/themes/midnight/theme.css">
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
Full authoring walkthrough: [`/docs/authoring/themes`](https://cssisawesome.com/docs/authoring/themes/). The contract (127 required + 49 optional tokens) is at [`scripts/theme-contract.json`](./scripts/theme-contract.json).
|
|
149
151
|
|
|
150
152
|
## Token contract
|
|
151
153
|
|
|
152
|
-
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,
|
|
154
|
+
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.
|
|
153
155
|
|
|
154
156
|
**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.
|
|
155
157
|
|
|
@@ -221,6 +223,7 @@ The CLI also carries the registry and the health check:
|
|
|
221
223
|
|
|
222
224
|
```bash
|
|
223
225
|
npx cia add --list # browse the recipe book
|
|
226
|
+
npx cia fix-theme t.css # move a theme onto current token names (--write to apply)
|
|
224
227
|
npx cia add bottom-nav # copy a recipe into your project — you own the pattern
|
|
225
228
|
npx cia analyze src/styles # design-system health: dead cia.* symbols, the
|
|
226
229
|
# space() scale trap, off-contract tokens (typos),
|
|
@@ -301,7 +304,7 @@ The scope is kept narrow: 8 `!important` declarations, all inside `@media print`
|
|
|
301
304
|
|
|
302
305
|
## MCP server (for AI agents)
|
|
303
306
|
|
|
304
|
-
cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **
|
|
307
|
+
cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **34 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) and `get_token_map` (that mapping as data, or how one path resolves). 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`](https://cssisawesome.com/docs/mcp/).
|
|
305
308
|
|
|
306
309
|
**Recommended — zero install:** use the dedicated [`css-is-awesome-mcp`](https://www.npmjs.com/package/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.
|
|
307
310
|
|
|
@@ -364,6 +367,8 @@ It also hosts the **[playground](https://cssisawesome.com/playground/)**: write
|
|
|
364
367
|
| `npm run build:css:all` | Compile all bundles (full + core + utilities + minified) + themes + token types |
|
|
365
368
|
| `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 |
|
|
366
369
|
| `npm run check:theme-drift` | Rebuild the themes into a scratch copy and fail if the committed artifacts don't match their SCSS sources |
|
|
370
|
+
| `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`](./VERSIONING.md) requires |
|
|
371
|
+
| `npm run check:rtl` | Audit the SCSS for physical properties that should be logical (`margin-left` → `margin-inline-start`) |
|
|
367
372
|
| `npm run build:token-types` | Generate `dist/tokens.d.ts` from the contract |
|
|
368
373
|
| `npm run dtcg-to-scss` | Convert DTCG-format design tokens into cia SCSS |
|
|
369
374
|
| `npm run lint` | ESLint on the Next.js app |
|
|
@@ -372,6 +377,9 @@ It also hosts the **[playground](https://cssisawesome.com/playground/)**: write
|
|
|
372
377
|
| `npm run validate-icons` | Validate the `core` icon pack against the 49-glyph contract |
|
|
373
378
|
| `npm run validate-api` | Assert the `css-is-awesome/api` barrel stays zero-emit |
|
|
374
379
|
| `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 |
|
|
380
|
+
| `npm run test:surfaces` | Assert cia never applies a page surface automatically — the guarantee that adding hero/band tokens changes nobody's existing page |
|
|
381
|
+
| `npm run test:tokens` | Exercise the design-tokens converter (DTCG / Tokens Studio / flat) against fixtures |
|
|
382
|
+
| `npm run verify:playground` | Compile real samples through the playground's in-browser Sass path in Node |
|
|
375
383
|
| `npm run pack:consumer` | Pack and install this build into a local consumer (defaults to `../boiler-project-ai`); `--dry-run` supported |
|
|
376
384
|
| `npm test` | Playwright suite — axe a11y checks + per-theme visual snapshots |
|
|
377
385
|
|
|
@@ -406,18 +414,18 @@ Full detail: [`/docs/testing`](https://cssisawesome.com/docs/testing/).
|
|
|
406
414
|
|
|
407
415
|
| Bundle | Size | Use case |
|
|
408
416
|
|---|---|---|
|
|
409
|
-
| `dist/tokens.css` | 2.
|
|
410
|
-
| `dist/css-is-awesome.core.min.css` | 2.
|
|
411
|
-
| `dist/css-is-awesome.utilities.min.css` | 4.
|
|
412
|
-
| `dist/css-is-awesome.min.css` | 7.
|
|
413
|
-
| Per-theme `themes/<name>/theme.css` |
|
|
417
|
+
| `dist/tokens.css` | 2.26 KB | Tokens only (`:where(:root)` CSS variables, no rules) — the purest mixin-first emit |
|
|
418
|
+
| `dist/css-is-awesome.core.min.css` | 2.42 KB | Tokens + resets, no utilities or components |
|
|
419
|
+
| `dist/css-is-awesome.utilities.min.css` | 4.79 KB | Every `cia-*` utility class, nothing else |
|
|
420
|
+
| `dist/css-is-awesome.min.css` | 7.99 KB | Full bundle (everything) |
|
|
421
|
+
| 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 |
|
|
414
422
|
| **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. |
|
|
415
423
|
|
|
416
424
|
## Status
|
|
417
425
|
|
|
418
426
|
**Stable, [published on npm](https://www.npmjs.com/package/css-is-awesome)** (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`](./VERSIONING.md) for the policy.
|
|
419
427
|
|
|
420
|
-
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](https://cssisawesome.com/playground/), the design-tokens on-ramp (`npx cia theme from-tokens` / `theme map`), and the
|
|
428
|
+
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](https://cssisawesome.com/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`](https://www.npmjs.com/package/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.
|
|
421
429
|
|
|
422
430
|
See [CHANGELOG.md](./CHANGELOG.md) for the full history and [MIGRATION.md](./MIGRATION.md) for the v0.7 → v0.8 and v0.8 → v1.0 upgrade paths.
|
|
423
431
|
|
package/bin/analyze.cjs
CHANGED
|
@@ -33,6 +33,18 @@ function loadContractTokens() {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Tokens the contract has superseded. They still resolve — cia deprecates
|
|
37
|
+
// rather than deletes — so this is a warning with a one-command fix, not an
|
|
38
|
+
// error. Read from the same map `cia fix-theme` and the validator use.
|
|
39
|
+
function loadDeprecated() {
|
|
40
|
+
try {
|
|
41
|
+
const c = JSON.parse(fs.readFileSync(CONTRACT_PATH, 'utf8'));
|
|
42
|
+
return c.deprecated && typeof c.deprecated === 'object' ? c.deprecated : {};
|
|
43
|
+
} catch {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
// Tiny Levenshtein (zero-dep). Only called on var(--x) misses, so cost is trivial.
|
|
37
49
|
function editDistance(a, b) {
|
|
38
50
|
const m = a.length, n = b.length;
|
|
@@ -256,7 +268,7 @@ function missingFocusVisible(strippedSrc) {
|
|
|
256
268
|
return [{ level: 'info', rule: 'missing-focus-visible', detail: 'interactive :hover/:active styling with no :focus-visible or focus-ring anywhere in this file — keyboard users may get no visible feedback' }];
|
|
257
269
|
}
|
|
258
270
|
|
|
259
|
-
function analyzeFile(file, symbols, contractTokens, extraNs) {
|
|
271
|
+
function analyzeFile(file, symbols, contractTokens, extraNs, deprecated) {
|
|
260
272
|
const raw = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
261
273
|
const src = stripComments(raw);
|
|
262
274
|
const ns = detectNamespaces(src, extraNs);
|
|
@@ -280,6 +292,37 @@ function analyzeFile(file, symbols, contractTokens, extraNs) {
|
|
|
280
292
|
}
|
|
281
293
|
}
|
|
282
294
|
}
|
|
295
|
+
// deprecated-token: still resolves, but the contract names a successor.
|
|
296
|
+
// Catches both a declaration (--old: value) and a reference (var(--old)).
|
|
297
|
+
// A plain scan, not a built regex: custom-property names are distinctive
|
|
298
|
+
// enough that a substring plus a name-boundary check is exact, and it does
|
|
299
|
+
// not depend on escaping a token name into a pattern.
|
|
300
|
+
if (deprecated && Object.keys(deprecated).length) {
|
|
301
|
+
const isNameChar = (ch) => /[A-Za-z0-9_-]/.test(ch);
|
|
302
|
+
for (const [tok, def] of Object.entries(deprecated)) {
|
|
303
|
+
let at = src.indexOf(tok);
|
|
304
|
+
let hit = false;
|
|
305
|
+
while (at !== -1) {
|
|
306
|
+
const after = src[at + tok.length];
|
|
307
|
+
// Reject a longer name that merely starts with this one.
|
|
308
|
+
if (after === undefined || !isNameChar(after)) { hit = true; break; }
|
|
309
|
+
at = src.indexOf(tok, at + 1);
|
|
310
|
+
}
|
|
311
|
+
if (!hit) continue;
|
|
312
|
+
const to = (def && def.replacedBy) || null;
|
|
313
|
+
let since = '';
|
|
314
|
+
if (def && def.since) {
|
|
315
|
+
since = ' (contract ' + def.since + (def.removeIn ? ', removed in ' + def.removeIn : '') + ')';
|
|
316
|
+
}
|
|
317
|
+
findings.push({
|
|
318
|
+
level: 'warn',
|
|
319
|
+
rule: 'deprecated-token',
|
|
320
|
+
detail: tok + ' is deprecated' + since + (to ? ' — use ' + to : '') +
|
|
321
|
+
'; your value still works. Run `npx cia fix-theme <file>` to rename it.',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
283
326
|
// off-contract-token: a var(--x) that's a near-miss of a real contract token
|
|
284
327
|
// (a typo → the declaration silently fails). Pure custom tokens — not close to
|
|
285
328
|
// any contract token — are the consumer's own and are left alone.
|
|
@@ -325,6 +368,7 @@ const RULE_CATEGORY = {
|
|
|
325
368
|
'unknown-symbol': 'API',
|
|
326
369
|
'space-scale': 'Spacing',
|
|
327
370
|
'off-contract-token': 'Contract',
|
|
371
|
+
'deprecated-token': 'Contract',
|
|
328
372
|
'off-scale-length': 'Spacing',
|
|
329
373
|
'hard-coded-color': 'Color',
|
|
330
374
|
bem: 'Naming',
|
|
@@ -364,8 +408,9 @@ async function run(args) {
|
|
|
364
408
|
|
|
365
409
|
const symbols = collectSymbols();
|
|
366
410
|
const contractTokens = loadContractTokens();
|
|
411
|
+
const deprecated = loadDeprecated();
|
|
367
412
|
const files = walkScss(target);
|
|
368
|
-
const results = files.map((f) => analyzeFile(f, symbols, contractTokens, extraNs)).filter((r) => r.findings.length || r.namespaces.length);
|
|
413
|
+
const results = files.map((f) => analyzeFile(f, symbols, contractTokens, extraNs, deprecated)).filter((r) => r.findings.length || r.namespaces.length);
|
|
369
414
|
|
|
370
415
|
const counts = { error: 0, warn: 0, info: 0 };
|
|
371
416
|
for (const r of results) for (const f of r.findings) counts[f.level]++;
|
package/bin/cia.cjs
CHANGED
|
@@ -43,6 +43,9 @@ Commands:
|
|
|
43
43
|
theme.css. \`cia theme from-tokens --help\`.
|
|
44
44
|
theme map The path → token mapping from-tokens applies, as
|
|
45
45
|
data: a table, \`--json\`, or \`--path <p>\` for one.
|
|
46
|
+
fix-theme <file> Move a theme onto current token names: rewrites
|
|
47
|
+
deprecated tokens to their replacements. Prints by
|
|
48
|
+
default, \`--write\` applies.
|
|
46
49
|
|
|
47
50
|
Examples:
|
|
48
51
|
cia migrate tailwind ./tailwind.config.js
|
|
@@ -50,6 +53,7 @@ Examples:
|
|
|
50
53
|
cia analyze src/styles
|
|
51
54
|
cia theme from-tokens tokens.json --name acme --out src/styles/acme.css
|
|
52
55
|
cia theme map --path color.text.primary
|
|
56
|
+
cia fix-theme src/styles/acme.css --write
|
|
53
57
|
|
|
54
58
|
Run \`cia <command> --help\` for command-specific help.
|
|
55
59
|
`;
|
|
@@ -158,6 +162,12 @@ async function main() {
|
|
|
158
162
|
return;
|
|
159
163
|
}
|
|
160
164
|
|
|
165
|
+
if (command === 'fix-theme') {
|
|
166
|
+
const { run } = require('./fix-theme.cjs');
|
|
167
|
+
await run(rest);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
161
171
|
if (command === 'theme') {
|
|
162
172
|
const [sub, ...themeArgs] = rest;
|
|
163
173
|
if (!sub || sub === '-h' || sub === '--help' || sub === 'help') {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cia fix-theme — rewrite a theme's deprecated tokens to their replacements.
|
|
3
|
+
*
|
|
4
|
+
* The CLI face of scripts/fix-theme.cjs (the MCP tool `fix_theme` and the
|
|
5
|
+
* in-process `handlers.fix_theme` are the same function). cia deprecates a
|
|
6
|
+
* token rather than deleting it, so the old declaration keeps working — this
|
|
7
|
+
* is the one command that moves you onto the current name.
|
|
8
|
+
*
|
|
9
|
+
* Only the property name changes. Values, comments, ordering and whitespace
|
|
10
|
+
* survive byte-for-byte, so applying this cannot alter how the theme looks.
|
|
11
|
+
*
|
|
12
|
+
* Reads and prints by default; writes only with --write.
|
|
13
|
+
*
|
|
14
|
+
* Exit codes: 0 nothing to do or fixed, 1 collisions need a human, 2 usage /
|
|
15
|
+
* input error.
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const HELP = `cia fix-theme — move a theme onto current token names
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
cia fix-theme <theme.css> [options]
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--write Apply the changes to the file (default: print, change nothing)
|
|
29
|
+
--json Machine-readable { css, changes, unchanged, summary }
|
|
30
|
+
-h, --help This text
|
|
31
|
+
|
|
32
|
+
What it does:
|
|
33
|
+
cia deprecates a token instead of deleting it, so your existing declaration
|
|
34
|
+
keeps working. This renames it to the replacement the contract names. Only
|
|
35
|
+
the property name moves — the value, your comments, the ordering and the
|
|
36
|
+
whitespace are untouched, so the rendered theme is identical.
|
|
37
|
+
|
|
38
|
+
If a block already declares the replacement, that line is left alone and
|
|
39
|
+
reported: merging two values is your decision, not the tool's.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
cia fix-theme src/styles/acme.css # show me what would change
|
|
43
|
+
cia fix-theme src/styles/acme.css --write # do it
|
|
44
|
+
cia fix-theme src/styles/acme.css --json # for a script
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const isTTY = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
48
|
+
const c = (code, s) => (isTTY ? `[${code}m${s}[0m` : s);
|
|
49
|
+
const bold = (s) => c('1', s);
|
|
50
|
+
const dim = (s) => c('2', s);
|
|
51
|
+
const green = (s) => c('32', s);
|
|
52
|
+
const yellow = (s) => c('33', s);
|
|
53
|
+
const red = (s) => c('31', s);
|
|
54
|
+
|
|
55
|
+
function parseArgs(argv) {
|
|
56
|
+
const out = { file: null, write: false, json: false, help: false };
|
|
57
|
+
for (const a of argv) {
|
|
58
|
+
if (a === '--write') out.write = true;
|
|
59
|
+
else if (a === '--json') out.json = true;
|
|
60
|
+
else if (a === '-h' || a === '--help' || a === 'help') out.help = true;
|
|
61
|
+
else if (a.startsWith('-')) throw new Error(`unknown option '${a}'`);
|
|
62
|
+
else if (out.file == null) out.file = a;
|
|
63
|
+
else throw new Error(`unexpected extra argument '${a}'`);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function run(argv) {
|
|
69
|
+
let args;
|
|
70
|
+
try {
|
|
71
|
+
args = parseArgs(argv || []);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
process.stderr.write(`${red('error:')} ${err.message}\n\n${HELP}`);
|
|
74
|
+
process.exitCode = 2;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (args.help || !args.file) {
|
|
78
|
+
process.stdout.write(HELP);
|
|
79
|
+
if (!args.help) process.exitCode = 2;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const abs = path.resolve(args.file);
|
|
84
|
+
let css;
|
|
85
|
+
try {
|
|
86
|
+
css = fs.readFileSync(abs, 'utf8');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
process.stderr.write(`${red('error:')} could not read ${args.file} — ${err.message}\n`);
|
|
89
|
+
process.exitCode = 2;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { fixTheme } = require(path.join(__dirname, '..', 'scripts', 'fix-theme.cjs'));
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = fixTheme({ css });
|
|
97
|
+
} catch (err) {
|
|
98
|
+
process.stderr.write(`${red('error:')} ${err.message}\n`);
|
|
99
|
+
process.exitCode = 2;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rewrites = result.changes.filter((x) => x.kind === 'rewrite');
|
|
104
|
+
const conflicts = result.changes.filter((x) => x.kind === 'conflict');
|
|
105
|
+
|
|
106
|
+
if (args.json) {
|
|
107
|
+
process.stdout.write(
|
|
108
|
+
JSON.stringify(
|
|
109
|
+
{ file: args.file, written: args.write && rewrites.length > 0, ...result },
|
|
110
|
+
null,
|
|
111
|
+
2
|
|
112
|
+
) + '\n'
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
const rel = path.relative(process.cwd(), abs) || args.file;
|
|
116
|
+
if (!result.changes.length) {
|
|
117
|
+
process.stdout.write(`${green('✓')} ${bold(rel)} ${dim('— no deprecated tokens; already current')}\n`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`${bold(rel)}\n`);
|
|
121
|
+
for (const ch of rewrites) {
|
|
122
|
+
process.stdout.write(` ${green('→')} line ${ch.line}: ${ch.from} ${dim('→')} ${bold(ch.to)}\n`);
|
|
123
|
+
process.stdout.write(` ${dim(ch.reason)}\n`);
|
|
124
|
+
}
|
|
125
|
+
for (const ch of conflicts) {
|
|
126
|
+
process.stdout.write(` ${yellow('!')} line ${ch.line}: ${ch.from} ${dim('left as-is')}\n`);
|
|
127
|
+
process.stdout.write(` ${dim(ch.reason)}\n`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (args.write && rewrites.length) {
|
|
132
|
+
try {
|
|
133
|
+
fs.writeFileSync(abs, result.css);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
process.stderr.write(`${red('error:')} could not write ${args.file} — ${err.message}\n`);
|
|
136
|
+
process.exitCode = 2;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!args.json) {
|
|
140
|
+
process.stdout.write(`\n${green('✓')} wrote ${rewrites.length} change(s) to ${bold(path.relative(process.cwd(), abs) || args.file)}\n`);
|
|
141
|
+
}
|
|
142
|
+
} else if (rewrites.length && !args.json) {
|
|
143
|
+
process.stdout.write(`\n${dim('nothing written — re-run with --write to apply')}\n`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Collisions are the one case a human has to settle, so say so in the exit
|
|
147
|
+
// code too: a pipeline should stop rather than assume the theme is current.
|
|
148
|
+
if (conflicts.length) process.exitCode = 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { run, parseArgs, HELP };
|
package/llm.txt
CHANGED
|
@@ -49,9 +49,9 @@ silent.
|
|
|
49
49
|
- **Mixins** — `cia.btn`, `cia.card`, `cia.accordion`, `cia.modal`, `cia.tooltip`, `cia.dropdown`, `cia.tabs`, `cia.copy-button`, `cia.hamburger`, `cia.drawer`, `cia.sheet`, `cia.dock`, `cia.stepper`, `cia.progress`, `cia.wizard-shell`, `cia.sidebar`, `cia.toolbar`, `cia.stack`, `cia.cluster`, `cia.switcher`, `cia.cover`, `cia.frame`, `cia.media`, `cia.contain`, `cia.focus-ring`, `cia.sr-only`, `cia.print`, `cia.print-base`, `cia.print-hidden`, `cia.print-only`, `cia.color`, `cia.space`, `cia.radius`, `cia.shadow`, `cia.font`, `cia.transition`, `cia.animate`, plus many more. **`cia.print-base` always forces `color-scheme: light` in print (paired `light-dark()` themes print their light branch), defines a themeable print palette (`--print-ink` / `--print-paper` / `--print-line` / `--print-muted`, default ink-on-white with grays) and rebinds the theme's own colour tokens (`--ink`, `--surface-*`, `--border-*`, `--code-*`) onto it — so every theme, dark-only (Terminal) included, prints legible ink-on-paper, and overriding a `--print-*` token restyles paper (a letterhead) without touching a component. Three opt-in flags, all default OFF:** `$link-urls` + `$link-origin` (print each link's full destination via `attr(href)`), `$page-numbers` (sheet numbers in the `@page` footer). `$legible` is a DEPRECATED no-op — the token rebind supersedes it and passing it warns.
|
|
50
50
|
- **24 themes / 8 families** — boilerplate, sketchbook, press, prism, cupertino, glass, graphite, terminal. Each family ships three files: an unsuffixed dual-mode base (both modes via `light-dark()`) plus pinned `-light` and `-dark` single-mode variants for `<link media>` pairing. Every one has exactly one SCSS source under `scss/themes/`; `public/themes/<name>/theme.css` is BUILD OUTPUT and is gated against its source by `npm run check:theme-drift` — never hand-edit it. MCP `list_themes` returns **24**. Say **24 themes across 8 families** when you need one number.
|
|
51
51
|
- **6 zero-JS interactive components** — accordion (`<details name>`), modal (`<dialog>`), tooltip (`popover="hint"`), dropdown (`[popover]` — the mixin re-asserts the UA's closed state and restores `display: flex` only under `:popover-open`, so menus never render permanently open on popover markup), tabs (radio + `:has()`), copy-button (Clipboard API via consumer-wired JS).
|
|
52
|
-
- **CLI** (`npx cia`) — `migrate tailwind|bootstrap|mui|chakra` (config → cia theme), `theme from-tokens <tokens.json> --name <slug>` (DTCG v2025.10 / Tokens Studio / flat `--token` JSON → a complete theme.css: required tokens the file lacks inherit from a shipped base, unmapped paths pass through and are reported, light/dark pairs become `light-dark()`, validator + WCAG audit run first; same function as the MCP `theme_from_tokens` tool), `theme map [--json | --path <p>]` (the path → token mapping from-tokens applies, as data; same as MCP `get_token_map`), `add <recipe>` (copy a recipe from the book into the project), `analyze [path]` (audit stylesheets against the installed API: dead `cia.*` symbols, the `space()` 1–9 trap, off-contract tokens (near-miss typos of real tokens only, never your own custom tokens), hard-coded hex, BEM; health score, CI exit codes). Low-noise on color: a hex in a `var(--token, #hex)` fallback is token-driven and a literal inside `@media print` is an intentional paper colour — neither is flagged.
|
|
52
|
+
- **CLI** (`npx cia`) — `migrate tailwind|bootstrap|mui|chakra` (config → cia theme), `theme from-tokens <tokens.json> --name <slug>` (DTCG v2025.10 / Tokens Studio / flat `--token` JSON → a complete theme.css: required tokens the file lacks inherit from a shipped base, unmapped paths pass through and are reported, light/dark pairs become `light-dark()`, validator + WCAG audit run first; same function as the MCP `theme_from_tokens` tool), `theme map [--json | --path <p>]` (the path → token mapping from-tokens applies, as data; same as MCP `get_token_map`), `add <recipe>` (copy a recipe from the book into the project), `analyze [path]` (audit stylesheets against the installed API: dead `cia.*` symbols, the `space()` 1–9 trap, off-contract tokens (near-miss typos of real tokens only, never your own custom tokens), hard-coded hex, BEM; health score, CI exit codes). Low-noise on color: a hex in a `var(--token, #hex)` fallback is token-driven and a literal inside `@media print` is an intentional paper colour — neither is flagged. `fix-theme <theme.css> [--write]` renames DEPRECATED tokens to their replacements (property only — values, comments and ordering survive, so the theme renders identically; prints by default, writes with `--write`; a block already declaring the replacement is reported, never merged).
|
|
53
53
|
- **Mobile navigation family** — `cia.hamburger` / `cia.drawer` / `cia.sheet` / `cia.dock` ride `[popover]` + CSS Grid, zero JS; recipes `mobile-nav` (hamburger + drawer) and `bottom-nav` (dock + sheets). House rule: **on phones things take the space they're in** — a `cia.dropdown` menu opens 1px under its full-width trigger at the trigger's exact width via CSS anchor positioning (`position-try-fallbacks: flip-block` flips it above at the screen bottom; set `width: auto` at `&[popover]` specificity or the UA's `[popover] { width: fit-content }` and the mixin's inset reset win). Full spec: AGENTS.md quick decision #8 and `/docs/mobile`.
|
|
54
|
-
- **
|
|
54
|
+
- **176 contract tokens — 127 required + 49 optional** — surfaces, ink, lines, colors, type, radius, shadow, blur, glow, motion, z-index, spacing, semantic aliases.
|
|
55
55
|
- **Spacing is themeable, and the numbered scale is the knob.** Themes declare `--space-0` … `--space-9` (contract-required). The t-shirt names (`--space-md`, `--space-lg` …) are contract-OPTIONAL and emitted by the library as `var()` aliases onto the numbered steps. `space(4)` compiles to `var(--space-4)`, so **theme the numbered step, never the alias** — setting `--space-md` alone leaves every component untouched. Library defaults emit under `:where(:root)` (specificity 0,0,0) so any theme declaration outranks them regardless of load order.
|
|
56
56
|
- **Per-component shape knobs are `--btn-radius`, `--card-radius`, `--input-radius`, `--modal-radius`, `--badge-radius`, `--tag-radius`** — they cascade from the generic radii (`--btn-radius: var(--radius-md, 0.25rem)`). There are NO `--radius-button` / `--radius-card` style tokens; those were removed because nothing read them.
|
|
57
57
|
- **Icons — two systems.** `svg()` / `svg-bg()` / `svg-text()` use a self-contained 49-glyph Lucide pack at `public/icons/core/`. Adding a glyph is drop-in: put `star.svg` in the folder and `cia.icon-svg(star)` works, no registration. Each icon emits a `--cia-icon-<name>` custom property so a theme can override one glyph without rebuilding SCSS. **`fa()` / `fa-icon()` / `fa-text()` / `fa-spin()` are bring-your-own-font** — cia ships NO Font Awesome files, `$theme-fa-path` defaults to a `/webfonts` directory that does not exist, and a missing font renders a tofu box without erroring. Default to `svg()` unless the user says they use Font Awesome.
|
|
@@ -105,7 +105,7 @@ Newspaper by day, hacker terminal by night. Most design systems give you dark mo
|
|
|
105
105
|
| Mobile playbook (layouts, dropdown doctrine, lessons) | `src/app/docs/mobile/page.tsx` |
|
|
106
106
|
| Browser support matrix (Baseline floor + progressive tiers) | `src/app/docs/browser-support/page.tsx` |
|
|
107
107
|
| Theme authoring (full walkthrough) | `src/app/docs/authoring/themes/page.tsx` |
|
|
108
|
-
| Theme contract (127 required +
|
|
108
|
+
| Theme contract (127 required + 49 optional grouped into 11 features, machine-readable) | `scripts/theme-contract.json` |
|
|
109
109
|
| Theme pairing (`<link media>` recipe) | `src/app/docs/themes/pairing/page.tsx` |
|
|
110
110
|
| CopyButton JS recipe | `src/app/docs/recipes/copy-button/page.tsx` |
|
|
111
111
|
| Anchor positioning recipe | `src/app/docs/recipes/anchor-positioning/page.tsx` |
|
|
@@ -129,7 +129,7 @@ Newspaper by day, hacker terminal by night. Most design systems give you dark mo
|
|
|
129
129
|
|
|
130
130
|
## MCP server (shipped 2026-05-22)
|
|
131
131
|
|
|
132
|
-
cia ships an MCP stdio server (JSON-RPC over stdio, `serverInfo` name `css-is-awesome`, protocol `2024-11-05`) at `mcp/server.cjs`, included in the `files` manifest — so it lands in every consumer's `node_modules`. **
|
|
132
|
+
cia ships an MCP stdio server (JSON-RPC over stdio, `serverInfo` name `css-is-awesome`, protocol `2024-11-05`) at `mcp/server.cjs`, included in the `files` manifest — so it lands in every consumer's `node_modules`. **34 tools** across 8 resource families: themes, mixins, functions, tokens (127 required + 49 optional), animations, components, recipes, doc readers. Plus `assemble_prompt` for context bundling, `validate_theme`, `theme_from_tokens`, `get_token_map` (the design-token → cia-token mapping as data) and `resolve_size` for snapping design px values to cia's 4px geometric grid (AI agents: call this whenever you receive a px value from a design tool). Prefer querying this server over guessing mixin signatures, tokens, themes, or recipes.
|
|
133
133
|
|
|
134
134
|
**Recommended: suggest the dedicated `css-is-awesome-mcp` package** — zero install,
|
|
135
135
|
SDK ships as a real dependency, no separate peer-install step:
|
|
@@ -198,4 +198,4 @@ Humans first. AI's role is to compose, not to lead the pitch.
|
|
|
198
198
|
|
|
199
199
|
---
|
|
200
200
|
|
|
201
|
-
*Generated 2026-05-21 for cia v0.8. Updated 2026-05-23 for the v1.0 architecture lock, 2026-08-30 for the theme single-source rework (24 themes, 127+36 contract, themeable spacing, `:root, :root[data-theme]` selector), 2026-09-04 for the shipped recipes count (5), the mobile-nav mixin family, and the dropdown popover guard, and 2026-09-19 for the 32-recipe book, 33-tool MCP, contract 1.2 (127 + 41 in 10 features), the playground and the design-tokens on-ramp. Update when the API surface changes.*
|
|
201
|
+
*Generated 2026-05-21 for cia v0.8. Updated 2026-05-23 for the v1.0 architecture lock, 2026-08-30 for the theme single-source rework (24 themes, 127+36 contract, themeable spacing, `:root, :root[data-theme]` selector), 2026-09-04 for the shipped recipes count (5), the mobile-nav mixin family, and the dropdown popover guard, and 2026-09-19 for the 32-recipe book, 33-tool MCP, contract 1.2 (127 + 41 in 10 features), the playground and the design-tokens on-ramp, and 2026-09-24 for contract 1.3 (127 required + 49 optional across 11 feature groups) and the page-surfaces hero/band tokens. Update when the API surface changes.*
|