css-is-awesome 1.16.1 → 1.18.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 CHANGED
@@ -338,7 +338,7 @@ 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 **31 tools** across 8 families:
341
+ Either way it exposes **32 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)
@@ -348,11 +348,11 @@ Either way it exposes **31 tools** across 8 families:
348
348
  - **Components** — `list_components`, `get_component`, `search_components`
349
349
  - **Recipes** — `list_recipes`, `get_recipe`
350
350
  - **Doc readers** — `read_llm_txt`, `read_changelog`, `read_migration`, `read_theming`, `read_agents`, `read_contract`, `read_three_tiers`, `read_readme`, `read_versioning`
351
- - **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)
351
+ - **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`)
352
352
 
353
353
  ## Other tooling (shipped)
354
354
 
355
- - **`cia` CLI** — ships as `bin/cia.cjs`, exposed as the `cia` bin. Three verbs:
355
+ - **`cia` CLI** — ships as `bin/cia.cjs`, exposed as the `cia` bin. Four verbs:
356
356
  `npx cia migrate tailwind|bootstrap [path]` parses another system's config and
357
357
  dumps a cia theme; `npx cia add <recipe>` (`--list` to browse) copies a recipe
358
358
  from the book into the project — own the pattern; `npx cia analyze [path]`
@@ -360,9 +360,14 @@ Either way it exposes **31 tools** across 8 families:
360
360
  the `space()` 1–9 scale trap, off-contract tokens (near-miss typos only), hard-coded
361
361
  hex colors, BEM chains — with a health score and CI-ready exit codes. Low-noise on
362
362
  color: a hex in a `var(--token, #hex)` fallback is token-driven, and a literal inside
363
- `@media print` is an intentional paper colour — neither is flagged. Run any verb with `--help`. (`cia init` remains
363
+ `@media print` is an intentional paper colour — neither is flagged; `npx cia theme from-tokens <tokens.json> --name <slug>`
364
+ turns a design-tokens file (DTCG v2025.10, Tokens Studio for Figma, or a flat `--token` map; format auto-detected)
365
+ into a complete theme.css — every required token the file lacks inherits from a shipped base theme (`--base`,
366
+ default boilerplate), unmapped paths pass through verbatim and are reported, a `--dark` file or paired
367
+ `color-light`/`color-dark` groups become `light-dark()`, and the validator + WCAG audit run before anything is
368
+ written. Same function as the MCP `theme_from_tokens` tool. Run any verb with `--help`. (`cia init` remains
364
369
  planned.)
365
- - **JSON token export** — DTCG-format token list in `figma-tokens/`.
370
+ - **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).
366
371
  - **`llm.txt`** — at the repo root and served from the docs site; single-fetch
367
372
  summary for any AI agent. Also readable over MCP via `read_llm_txt`.
368
373
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ # [1.18.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.17.0...v1.18.0) (2026-09-19)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **site:** playground editor no longer boots from stale text when a share link decodes early ([3f0748f](https://github.com/Jerry2d3d/css-is-awesome/commit/3f0748f28b8a70471f1f61485733872c6ccdd6a9))
9
+
10
+
11
+ ### Features
12
+
13
+ * **contract:** feature groups for optional tokens — contract 1.2 ([cc98e1f](https://github.com/Jerry2d3d/css-is-awesome/commit/cc98e1f9397ae89ef65b70557a942b5b561a0f85))
14
+
15
+ # [1.17.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.16.1...v1.17.0) (2026-09-19)
16
+
17
+
18
+ ### Features
19
+
20
+ * **cli:** cia theme from-tokens — design-tokens JSON → validated theme.css ([066b484](https://github.com/Jerry2d3d/css-is-awesome/commit/066b4849d2e937f0362e8f21cb212f3fcc144c5f))
21
+ * **mcp:** theme_from_tokens tool + in-process handler (32 tools) ([9dc90c4](https://github.com/Jerry2d3d/css-is-awesome/commit/9dc90c40574a5e21b7b724f97792b0fd9c4182b0))
22
+
3
23
  ## [1.16.1](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.16.0...v1.16.1) (2026-09-18)
4
24
 
5
25
 
package/CONTRACT.md CHANGED
@@ -306,6 +306,25 @@ Paper themes declare these as `none` / `transparent` so a swap to a glass or pho
306
306
 
307
307
  ---
308
308
 
309
+ ## Optional tokens by feature (contract 1.2)
310
+
311
+ Every optional token belongs to exactly one **feature** in `scripts/theme-contract.json` (`features`), so a validator, installer or agent can say *"this theme is missing the tokens for print"* instead of listing all 41 optional names. The validator's info line reports counts per feature; `--show-optional` lists them grouped. `npm run check:contract` fails the build if an optional token is in no feature, in two, or if a feature names a required token.
312
+
313
+ | Feature | Tokens | Enables |
314
+ | --- | --- | --- |
315
+ | `density` | `--space-unit` | The density knob: shipped themes derive every --space-N from this one unit via calc(); set it to tighten or open up the whole UI (theme editor slider). |
316
+ | `spacing-aliases` | `--space-2xs`, `--space-xs`, `--space-sm`, `--space-md`, `--space-lg`, `--space-xl` | T-shirt spacing names (2xs–xl) for consumer CSS that prefers them; the library reads the numbered scale, so these are pure aliases. |
317
+ | `print` | `--print-ink`, `--print-paper`, `--print-line`, `--print-muted` | A themed printed page: cia.print-base rebinds ink/surface/border/code tokens onto this palette inside @media print. Without them the ink-on-white default applies. |
318
+ | `component-radius` | `--btn-radius`, `--card-radius`, `--input-radius`, `--modal-radius`, `--badge-radius`, `--tag-radius` | Per-component corner radius overrides (buttons, cards, inputs, modals, badges, tags) without rebuilding SCSS; each falls back to the generic --radius-* scale. |
319
+ | `component-shadows` | `--shadow-button`, `--shadow-card`, `--shadow-dropdown`, `--shadow-input-focus`, `--shadow-modal`, `--shadow-popover`, `--shadow-tooltip`, `--shadow-text` | Per-component elevation overrides; each falls back to the generic --shadow-* scale. |
320
+ | `component-motion` | `--duration-button-hover`, `--duration-modal-open`, `--duration-toast-slide` | Per-component durations for button hover, modal open and toast slide; each falls back to the generic --duration-* scale. |
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
+ | `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
+ | `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
+ | `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
+
309
328
  ## Print (optional)
310
329
 
311
330
  Four tokens describing the printed page. They're **optional** — `cia.print-base` (included once, at the stylesheet root) emits every one of them on `:root` inside `@media print` with a clean ink-on-white default, and every print rule reads them via `var(--print-*)`. A theme MAY override them in its own `@media print` block for a paper identity (Press does, for a newsprint look); a theme that sets none of them prints the plain default.
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 — 31 tools covering themes, mixins, functions, tokens, recipes, components and theme validation.
17
+ **Then connect the MCP server** and stop guessing at signatures. It answers from the real source — 32 tools covering themes, mixins, functions, tokens, recipes, components, theme validation and theme generation from design tokens.
18
18
 
19
19
  ```json
20
20
  {
@@ -226,6 +226,10 @@ npx cia analyze src/styles # design-system health: dead cia.* symbols, the
226
226
  # space() scale trap, off-contract tokens (typos),
227
227
  # off-scale lengths, hard-coded colors, BEM creep,
228
228
  # missing focus-visible styling
229
+ npx cia theme from-tokens tokens.json --name acme --out src/styles/acme.css
230
+ # design tokens (DTCG v2025.10, Tokens Studio, or a
231
+ # flat --token map) → a complete, validated theme.css;
232
+ # missing required tokens inherit from a shipped base
229
233
  ```
230
234
 
231
235
  `cia 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`](https://cssisawesome.com/docs/analyzer/).
@@ -294,7 +298,7 @@ The scope is kept narrow: 8 `!important` declarations, all inside `@media print`
294
298
 
295
299
  ## MCP server (for AI agents)
296
300
 
297
- cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **31 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) and `validate_theme` (run the real theme validator on CSS you just wrote). 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/).
301
+ cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **32 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) and `theme_from_tokens` (design-tokens JSON → a complete, validated theme.css). 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/).
298
302
 
299
303
  **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.
300
304
 
@@ -410,7 +414,7 @@ Full detail: [`/docs/testing`](https://cssisawesome.com/docs/testing/).
410
414
 
411
415
  **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.
412
416
 
413
- 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, and the 31-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 JavaScript by hard rule.
417
+ 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, and the 32-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 JavaScript by hard rule.
414
418
 
415
419
  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.
416
420
 
package/VERSIONING.md CHANGED
@@ -43,6 +43,7 @@ Additive, non-breaking changes.
43
43
  | New public CSS class | `.cia-grid-auto-fit` added |
44
44
  | New public SCSS mixin | `m.cluster($gap)` added |
45
45
  | New optional token added to contract (`"1"` → `"1.1"`) | `--dropdown-offset-y` added to component section |
46
+ | Contract metadata added (e.g. the `features` map, `"1.1"` → `"1.2"`, 2026-09-18) | Additive keys — old validators ignore them |
46
47
  | Required token relaxed to optional (contract minor bump) | `--space-unit` required → optional, contract `"1"` → `"1.1"` (2026-09-18) |
47
48
  | New theme or recipe shipped | `prism` family added; `mobile-nav` recipe added |
48
49
  | New utility class (`.cia-*`) | `.cia-text-balance` added |
package/bin/cia.cjs CHANGED
@@ -11,6 +11,7 @@
11
11
  * migrate bootstrap — parse Bootstrap SCSS/CSS vars + dump theme JSON
12
12
  * migrate mui — parse a MUI createTheme() result + dump theme JSON
13
13
  * migrate chakra — parse a Chakra extendTheme() result + dump theme JSON
14
+ * theme from-tokens — design-tokens JSON (DTCG / Tokens Studio) → validated theme.css
14
15
  *
15
16
  * cia core ships ZERO JavaScript in the `files` manifest. The CLI lives in
16
17
  * `bin/` which is explicitly allowed per the architecture lock — same path
@@ -36,11 +37,15 @@ Commands:
36
37
  analyze [path] Design-system health check: dead cia.* symbols,
37
38
  the space() scale trap, hard-coded colors, BEM,
38
39
  hand-written area maps.
40
+ theme from-tokens <f> Design-tokens JSON (DTCG v2025.10, Tokens Studio,
41
+ or flat --token map) → a complete, validated
42
+ theme.css. \`cia theme from-tokens --help\`.
39
43
 
40
44
  Examples:
41
45
  cia migrate tailwind ./tailwind.config.js
42
46
  cia add bottom-nav
43
47
  cia analyze src/styles
48
+ cia theme from-tokens tokens.json --name acme --out src/styles/acme.css
44
49
 
45
50
  Run \`cia <command> --help\` for command-specific help.
46
51
  `;
@@ -149,6 +154,20 @@ async function main() {
149
154
  return;
150
155
  }
151
156
 
157
+ if (command === 'theme') {
158
+ const [sub, ...themeArgs] = rest;
159
+ if (!sub || sub === '-h' || sub === '--help' || sub === 'help') {
160
+ process.stdout.write(require('./theme-from-tokens.cjs').HELP);
161
+ return;
162
+ }
163
+ if (sub === 'from-tokens') {
164
+ const { run } = require('./theme-from-tokens.cjs');
165
+ await run(themeArgs);
166
+ return;
167
+ }
168
+ fail(`unknown theme subcommand '${sub}'. Available: from-tokens.`);
169
+ }
170
+
152
171
  fail(`unknown command '${command}'. Run \`cia --help\` for usage.`);
153
172
  }
154
173
 
@@ -0,0 +1,139 @@
1
+ /**
2
+ * cia theme from-tokens — design-tokens JSON → validated cia theme.css.
3
+ *
4
+ * The CLI face of scripts/tokens-to-theme.cjs (the MCP tool
5
+ * `theme_from_tokens` and the in-process `handlers.theme_from_tokens` are the
6
+ * same function). Zero dependencies, no Sass: the theme is emitted as CSS in
7
+ * the exact shape the shipped themes use, then the shipped validator + WCAG
8
+ * audit run on it before anything is written.
9
+ *
10
+ * Exit codes: 0 valid (or --no-validate), 1 contract/a11y failure (a11y only
11
+ * unless --allow-a11y-fail), 2 usage / input error.
12
+ */
13
+ 'use strict';
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const HELP = `cia theme from-tokens — design-tokens JSON → validated cia theme.css
18
+
19
+ Usage:
20
+ cia theme from-tokens <tokens.json> --name <slug> [options]
21
+
22
+ Options:
23
+ --name <slug> Theme name (kebab-case). Required.
24
+ --format <f> auto | dtcg | tokens-studio | cia-flat (default: auto)
25
+ --base <theme> Shipped theme that supplies every REQUIRED token the file
26
+ does not (default: boilerplate). \`--base list\` prints them.
27
+ --dark <dark.json> Second tokens file for dark mode → light-dark() values.
28
+ --mode light|dark Single-mode color-scheme when no dark side (default: light).
29
+ --out <theme.css> Write the CSS here (default: stdout).
30
+ --json Print { report, validation } as JSON to stdout
31
+ (CSS goes to --out only).
32
+ --no-validate Skip the validator + WCAG audit.
33
+ --allow-a11y-fail Do not exit non-zero on a11y FAILs (report only).
34
+
35
+ Formats:
36
+ dtcg DTCG v2025.10 — leaves carry $value (+ $type); {aliases} resolve.
37
+ tokens-studio Tokens Studio for Figma — leaves carry value + type; single set
38
+ or multi-set export ($metadata.tokenSetOrder); {aliases} resolve.
39
+ Paired top-level groups (color-light + color-dark, or
40
+ light + dark) become light-dark() automatically.
41
+ cia-flat { "--brand-primary": "#3A5FCD", ... } passthrough.
42
+
43
+ Minimum content: none. Whatever the file does not supply is inherited from the
44
+ base theme and listed in the report, so the output is always contract-complete.
45
+
46
+ Examples:
47
+ cia theme from-tokens tokens.json --name acme --out src/styles/acme.css
48
+ cia theme from-tokens light.json --dark dark.json --name acme --json
49
+ cia theme from-tokens brand.json --name acme --base press --format dtcg
50
+ `;
51
+
52
+ function parseArgs(argv) {
53
+ const opts = { format: 'auto', base: 'boilerplate', mode: 'light', validate: true, allowA11yFail: false, json: false };
54
+ const positional = [];
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const a = argv[i];
57
+ const next = () => { const v = argv[++i]; if (v === undefined) throw new Error(`${a} needs a value`); return v; };
58
+ if (a === '-h' || a === '--help') opts.help = true;
59
+ else if (a === '--name') opts.name = next();
60
+ else if (a === '--format') opts.format = next();
61
+ else if (a === '--base') opts.base = next();
62
+ else if (a === '--dark') opts.dark = next();
63
+ else if (a === '--mode') opts.mode = next();
64
+ else if (a === '--out') opts.out = next();
65
+ else if (a === '--json') opts.json = true;
66
+ else if (a === '--no-validate') opts.validate = false;
67
+ else if (a === '--allow-a11y-fail') opts.allowA11yFail = true;
68
+ else if (a.startsWith('-')) throw new Error(`unknown option ${a}`);
69
+ else positional.push(a);
70
+ }
71
+ opts.input = positional[0];
72
+ return opts;
73
+ }
74
+
75
+ function readJson(file) {
76
+ const abs = path.resolve(file);
77
+ let text;
78
+ try { text = fs.readFileSync(abs, 'utf8'); } catch (e) { throw new Error(`cannot read ${file}: ${e.message}`); }
79
+ try { return JSON.parse(text); } catch (e) { throw new Error(`${file} is not valid JSON: ${e.message}`); }
80
+ }
81
+
82
+ async function run(argv) {
83
+ let opts;
84
+ try { opts = parseArgs(argv); } catch (e) { process.stderr.write(`cia theme: ${e.message}\n`); process.exit(2); }
85
+ if (opts.help || !opts.input && opts.base !== 'list') { process.stdout.write(HELP); return; }
86
+
87
+ const { themeFromTokens, listBases } = require('../scripts/tokens-to-theme.cjs');
88
+ if (opts.base === 'list') { process.stdout.write(listBases(path.join(__dirname, '..')).join('\n') + '\n'); return; }
89
+ if (!opts.name) { process.stderr.write('cia theme: --name <slug> is required\n'); process.exit(2); }
90
+
91
+ let result;
92
+ try {
93
+ result = themeFromTokens({
94
+ tokens: readJson(opts.input),
95
+ dark: opts.dark ? readJson(opts.dark) : undefined,
96
+ name: opts.name,
97
+ format: opts.format,
98
+ base: opts.base,
99
+ mode: opts.mode,
100
+ validate: opts.validate,
101
+ });
102
+ } catch (e) {
103
+ process.stderr.write(`cia theme: ${e.message}\n`);
104
+ process.exit(2);
105
+ }
106
+
107
+ const { css, report, validation } = result;
108
+ if (opts.out) fs.writeFileSync(path.resolve(opts.out), css, 'utf8');
109
+
110
+ if (opts.json) {
111
+ process.stdout.write(JSON.stringify({ report, validation, out: opts.out || null }, null, 2) + '\n');
112
+ } else {
113
+ if (!opts.out) process.stdout.write(css);
114
+ const c = report.counts;
115
+ const lines = [
116
+ `theme "${report.name}" — ${report.format}${report.darkMode ? ' (light + dark)' : ''} · base ${report.base}`,
117
+ ` from tokens ${c.fromTokens}`,
118
+ ` inherited ${c.inherited} (required tokens the file did not supply)`,
119
+ ` passthrough ${c.unmapped} (not contract tokens — emitted verbatim)`,
120
+ c.skipped ? ` skipped ${c.skipped} (composite values with no single-token home)` : null,
121
+ opts.out ? ` written ${opts.out}` : null,
122
+ ].filter(Boolean);
123
+ process.stderr.write(lines.join('\n') + '\n');
124
+ if (validation) {
125
+ const { reportResult, reportA11yForTheme } = require('../scripts/theme-validator.js');
126
+ reportResult(validation);
127
+ const themes = validation.mode === 'consolidated' ? validation.themes : [{ name: report.name, a11y: validation.a11y }];
128
+ for (const t of themes) if (t.a11y) reportA11yForTheme(t.name, t.a11y, ' ');
129
+ }
130
+ }
131
+
132
+ if (validation) {
133
+ const a11yFail = validation.a11ySummary ? validation.a11ySummary.fail : 0;
134
+ if (!validation.ok) process.exit(1);
135
+ if (a11yFail && !opts.allowA11yFail) process.exit(1);
136
+ }
137
+ }
138
+
139
+ module.exports = { run, parseArgs, HELP };
package/dist/tokens.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated from scripts/theme-contract.json on 2026-09-18.
1
+ // Generated from scripts/theme-contract.json on 2026-09-19.
2
2
  // Do not edit by hand. Run `npm run build:token-types` to regenerate.
3
3
 
4
4
  /** Every CSS custom property cia themes are required to declare. */
@@ -22,6 +22,21 @@ The `.scss` siblings exist for consumers who want to `@import` Figma-exported
22
22
  tokens directly without going through `tokens.json`. They are not consumed by
23
23
  the main `scss/` build — `scss/main.scss` pulls from `scss/theme/`.
24
24
 
25
+ ## Round trip: `tokens.json` → a theme
26
+
27
+ `tokens.json` is a Tokens Studio export and is consumed by the design-tokens
28
+ on-ramp shipped since 1.17.0:
29
+
30
+ ```bash
31
+ npx cia theme from-tokens node_modules/css-is-awesome/figma-tokens/tokens.json --name figma-demo --out figma-demo.css
32
+ ```
33
+
34
+ Its paired `color-light` / `color-dark` groups become one `light-dark()` theme;
35
+ `brand`, `spacing`, `font-*`, `line-height`, `border-radius` and `shadow` map by
36
+ the generic path rule; anything without a contract home (e.g. `brand.accent`)
37
+ is emitted verbatim and listed in the report. The same function is the MCP
38
+ `theme_from_tokens` tool. Input contract: `/docs/authoring/themes#from-design-tokens`.
39
+
25
40
  ## Direction of truth
26
41
 
27
42
  **Hand-maintained.** Keep in sync with `scss/theme/` when tokens change.
package/llm.txt CHANGED
@@ -48,7 +48,7 @@ silent.
48
48
  - **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.
49
49
  - **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.
50
50
  - **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).
51
- - **CLI** (`npx cia`) — `migrate tailwind|bootstrap` (config → cia theme), `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.
51
+ - **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), `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
52
  - **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`.
53
53
  - **168 contract tokens — 127 required + 41 optional** — surfaces, ink, lines, colors, type, radius, shadow, blur, glow, motion, z-index, spacing, semantic aliases.
54
54
  - **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.
@@ -104,7 +104,7 @@ Newspaper by day, hacker terminal by night. Most design systems give you dark mo
104
104
  | Mobile playbook (layouts, dropdown doctrine, lessons) | `src/app/docs/mobile/page.tsx` |
105
105
  | Browser support matrix (Baseline floor + progressive tiers) | `src/app/docs/browser-support/page.tsx` |
106
106
  | Theme authoring (full walkthrough) | `src/app/docs/authoring/themes/page.tsx` |
107
- | Theme contract (127 required + 41 optional, machine-readable) | `scripts/theme-contract.json` |
107
+ | Theme contract (127 required + 41 optional grouped into 10 features, machine-readable) | `scripts/theme-contract.json` |
108
108
  | Theme pairing (`<link media>` recipe) | `src/app/docs/themes/pairing/page.tsx` |
109
109
  | CopyButton JS recipe | `src/app/docs/recipes/copy-button/page.tsx` |
110
110
  | Anchor positioning recipe | `src/app/docs/recipes/anchor-positioning/page.tsx` |
@@ -128,7 +128,7 @@ Newspaper by day, hacker terminal by night. Most design systems give you dark mo
128
128
 
129
129
  ## MCP server (shipped 2026-05-22)
130
130
 
131
- 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`. **30 tools** across 8 resource families: themes, mixins, functions, tokens (127 required + 36 optional), animations, components, recipes, doc readers. Plus `assemble_prompt` for context bundling 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.
131
+ 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`. **32 tools** across 8 resource families: themes, mixins, functions, tokens (127 required + 36 optional), animations, components, recipes, doc readers. Plus `assemble_prompt` for context bundling 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.
132
132
 
133
133
  **Recommended: suggest the dedicated `css-is-awesome-mcp` package** — zero install,
134
134
  SDK ships as a real dependency, no separate peer-install step:
package/mcp/server.cjs CHANGED
@@ -19,9 +19,10 @@
19
19
  * read_theming, read_agents, read_contract,
20
20
  * read_three_tiers, read_readme, read_versioning
21
21
  * Sizing: resolve_size
22
+ * Themes (build): theme_from_tokens — design-tokens JSON → validated theme.css
22
23
  * Prompt: assemble_prompt(intent[, args])
23
24
  *
24
- * 31 tools total.
25
+ * 32 tools total.
25
26
  *
26
27
  * Discovery model: filesystem scan, no database. Parses SCSS files with
27
28
  * focused regex (no full SCSS AST). Tokens come from the authoritative
@@ -425,6 +426,12 @@ function loadTokenContract() {
425
426
  return { required: [], optional: [], byName: {}, byCategory: {} };
426
427
  }
427
428
  const optional = Array.isArray(contract.optional) ? contract.optional : [];
429
+ // Contract 1.2: optional tokens are grouped by the feature they enable.
430
+ const features = contract.features && typeof contract.features === 'object' ? contract.features : {};
431
+ const featureOf = {};
432
+ for (const [feature, def] of Object.entries(features)) {
433
+ for (const t of (def && Array.isArray(def.tokens)) ? def.tokens : []) featureOf[t] = feature;
434
+ }
428
435
 
429
436
  const byName = {};
430
437
  const byCategory = {};
@@ -459,11 +466,11 @@ function loadTokenContract() {
459
466
  }
460
467
  for (const t of optional) {
461
468
  const category = categorize(t);
462
- byName[t] = { name: t, category, required: false };
469
+ byName[t] = { name: t, category, required: false, feature: featureOf[t] || null };
463
470
  (byCategory[category] = byCategory[category] || []).push(t);
464
471
  }
465
472
 
466
- return { required: contract.required, optional, byName, byCategory };
473
+ return { required: contract.required, optional, features, byName, byCategory };
467
474
  }
468
475
 
469
476
  /**
@@ -698,6 +705,18 @@ const handlers = {
698
705
  return validateText(css, loadContract(), { label: label || undefined });
699
706
  },
700
707
 
708
+ // Design-tokens JSON (DTCG v2025.10 / Tokens Studio / flat --token map) →
709
+ // a complete theme.css in the shipped shape, validated + contrast-audited.
710
+ // Same function as `cia theme from-tokens`; reachable in-process through
711
+ // module.exports.handlers so an inventory builder can call it without a
712
+ // transport. See scripts/tokens-to-theme.cjs for the input contract.
713
+ theme_from_tokens({ tokens, name, format, base, dark, mode, validate } = {}) {
714
+ if (tokens == null) throw new Error('theme_from_tokens: tokens is required (object or JSON string)');
715
+ if (!name) throw new Error('theme_from_tokens: name is required');
716
+ const { themeFromTokens } = require(path.join(SCRIPTS_DIR, 'tokens-to-theme.cjs'));
717
+ return themeFromTokens({ tokens, name, format, base, dark, mode, validate });
718
+ },
719
+
701
720
  // ─── Mixins ────────────────────────────────────────────────────────────
702
721
 
703
722
  list_mixins({ category, component, limit = 500, offset = 0 } = {}) {
@@ -841,6 +860,8 @@ const handlers = {
841
860
  name: entry.name,
842
861
  category: entry.category,
843
862
  required: entry.required,
863
+ // Contract 1.2: the feature an OPTIONAL token enables (null for required).
864
+ feature: entry.required ? null : (entry.feature || null),
844
865
  themeValues,
845
866
  referencedBy: referencedBy.slice(0, 20),
846
867
  };
@@ -1354,6 +1375,27 @@ async function startServer() {
1354
1375
  },
1355
1376
  }, async (a) => ok(handlers.validate_theme(a || {})));
1356
1377
 
1378
+ server.registerTool('theme_from_tokens', {
1379
+ description:
1380
+ 'Build a complete, validated cia theme.css from a design-tokens JSON — DTCG v2025.10 ({ $value, $type }, ' +
1381
+ '{aliases} resolved), a Tokens Studio for Figma export ({ value, type }, single or multi-set), or a flat ' +
1382
+ '{ "--token": value } map. Format is auto-detected. Every REQUIRED contract token the file does not supply ' +
1383
+ 'is inherited from a shipped base theme (default boilerplate) and listed in report.inherited, so the output ' +
1384
+ 'is always contract-complete; unmapped paths are emitted verbatim and listed in report.unmapped, never ' +
1385
+ 'dropped. Pass `dark` (same format) or a single file with paired color-light/color-dark groups to get ' +
1386
+ 'light-dark() values. Returns { css, report, validation } — validation is the same result validate_theme ' +
1387
+ 'gives, run on the CSS before you write it anywhere.',
1388
+ inputSchema: {
1389
+ tokens: z.union([z.record(z.any()), z.string()]).describe('The tokens JSON (object, or a JSON string).'),
1390
+ name: z.string().describe('Theme name — kebab-case slug, becomes [data-theme="<name>"].'),
1391
+ format: z.enum(['auto', 'dtcg', 'tokens-studio', 'cia-flat']).optional().describe('Default auto.'),
1392
+ base: z.string().optional().describe('Shipped theme that supplies missing required tokens. Default boilerplate.'),
1393
+ dark: z.union([z.record(z.any()), z.string()]).optional().describe('Optional dark-mode tokens (same format) → light-dark() values.'),
1394
+ mode: z.enum(['light', 'dark']).optional().describe('Single-mode color-scheme when there is no dark side. Default light.'),
1395
+ validate: z.boolean().optional().describe('Run the validator + WCAG audit (default true).'),
1396
+ },
1397
+ }, async (a) => ok(handlers.theme_from_tokens(a || {})));
1398
+
1357
1399
  // Mixins
1358
1400
  server.registerTool('list_mixins', {
1359
1401
  description: `List all ${MIXIN_COUNT} public @mixins across core, layout, animation, icons, generator, per-component and recipe sources. Filter by category (core/layout/animation/icons/generator/component/recipe) or component name.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-is-awesome",
3
- "version": "1.16.1",
3
+ "version": "1.18.0",
4
4
  "description": "A token-driven SCSS design system with light/dark theming, semantic color tokens, and a 800+ LOC mixin API.",
5
5
  "homepage": "https://github.com/Jerry2d3d/css-is-awesome#readme",
6
6
  "bugs": {
@@ -69,6 +69,7 @@
69
69
  "mcp",
70
70
  "scripts/theme-contract.json",
71
71
  "scripts/theme-validator.js",
72
+ "scripts/tokens-to-theme.cjs",
72
73
  "scripts/theme-a11y.js",
73
74
  "scripts/audit-pairs.json",
74
75
  "scripts/icon-validator.js",
@@ -117,6 +118,7 @@
117
118
  "validate-api": "node scripts/validate-api.mjs",
118
119
  "validate-package": "node scripts/validate-package.mjs",
119
120
  "validate-recipes": "node scripts/validate-recipes.mjs",
121
+ "test:tokens": "node --test scripts/test-tokens-to-theme.mjs",
120
122
  "size-budget": "node scripts/size-budget.mjs",
121
123
  "size-report": "node scripts/size-budget.mjs --report",
122
124
  "coverage:api": "node scripts/api-coverage.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.1",
3
- "description": "Authoritative token contract for css-is-awesome themes. Every theme.css MUST declare every token in `required`. Tokens in `optional` are reported by the validator as info, never as failures \u2014 the library or the theme generator supplies a default. Source of truth is CONTRACT.md (human-readable) and public/theme.css (Sketchbook reference). Contract 1.1 (2026-09-18): --space-unit relaxed from required to optional; it had been added as required in library 1.12.0, which VERSIONING.md forbids in a MINOR.",
2
+ "version": "1.2",
3
+ "description": "Authoritative token contract for css-is-awesome themes. Every theme.css MUST declare every token in `required`. Tokens in `optional` are reported by the validator as info, never as failures \u2014 the library or the theme generator supplies a default. Source of truth is CONTRACT.md (human-readable) and public/theme.css (Sketchbook reference). Contract 1.1 (2026-09-18): --space-unit relaxed from required to optional; it had been added as required in library 1.12.0, which VERSIONING.md forbids in a MINOR. Contract 1.2 (2026-09-18): `features` groups every optional token by the capability it enables, so a validator or installer can say \"missing the tokens for print\" instead of listing all optional tokens.",
4
4
  "required": [
5
5
  "--action-primary-active",
6
6
  "--action-primary-default",
@@ -172,5 +172,98 @@
172
172
  "--space-xs",
173
173
  "--tag-radius",
174
174
  "--touch-target-min"
175
- ]
175
+ ],
176
+ "features": {
177
+ "density": {
178
+ "enables": "The density knob: shipped themes derive every --space-N from this one unit via calc(); set it to tighten or open up the whole UI (theme editor slider).",
179
+ "tokens": [
180
+ "--space-unit"
181
+ ]
182
+ },
183
+ "spacing-aliases": {
184
+ "enables": "T-shirt spacing names (2xs\u2013xl) for consumer CSS that prefers them; the library reads the numbered scale, so these are pure aliases.",
185
+ "tokens": [
186
+ "--space-2xs",
187
+ "--space-xs",
188
+ "--space-sm",
189
+ "--space-md",
190
+ "--space-lg",
191
+ "--space-xl"
192
+ ]
193
+ },
194
+ "print": {
195
+ "enables": "A themed printed page: cia.print-base rebinds ink/surface/border/code tokens onto this palette inside @media print. Without them the ink-on-white default applies.",
196
+ "tokens": [
197
+ "--print-ink",
198
+ "--print-paper",
199
+ "--print-line",
200
+ "--print-muted"
201
+ ]
202
+ },
203
+ "component-radius": {
204
+ "enables": "Per-component corner radius overrides (buttons, cards, inputs, modals, badges, tags) without rebuilding SCSS; each falls back to the generic --radius-* scale.",
205
+ "tokens": [
206
+ "--btn-radius",
207
+ "--card-radius",
208
+ "--input-radius",
209
+ "--modal-radius",
210
+ "--badge-radius",
211
+ "--tag-radius"
212
+ ]
213
+ },
214
+ "component-shadows": {
215
+ "enables": "Per-component elevation overrides; each falls back to the generic --shadow-* scale.",
216
+ "tokens": [
217
+ "--shadow-button",
218
+ "--shadow-card",
219
+ "--shadow-dropdown",
220
+ "--shadow-input-focus",
221
+ "--shadow-modal",
222
+ "--shadow-popover",
223
+ "--shadow-tooltip",
224
+ "--shadow-text"
225
+ ]
226
+ },
227
+ "component-motion": {
228
+ "enables": "Per-component durations for button hover, modal open and toast slide; each falls back to the generic --duration-* scale.",
229
+ "tokens": [
230
+ "--duration-button-hover",
231
+ "--duration-modal-open",
232
+ "--duration-toast-slide"
233
+ ]
234
+ },
235
+ "surfaces-extended": {
236
+ "enables": "Extra background layers (elevated, hero, overlay, scrim) for themes that want more than the required surface set; each falls back to a required surface.",
237
+ "tokens": [
238
+ "--background-elevated",
239
+ "--background-hero",
240
+ "--background-overlay",
241
+ "--background-scrim"
242
+ ]
243
+ },
244
+ "borders-extended": {
245
+ "enables": "Per-context border colours (card, divider, focus ring, input); each falls back to --border-default / --border-focus.",
246
+ "tokens": [
247
+ "--border-card",
248
+ "--border-divider",
249
+ "--border-focus-ring",
250
+ "--border-input"
251
+ ]
252
+ },
253
+ "logo": {
254
+ "enables": "Theme-aware logo assets (default, mark, monochrome, wordmark) as url() or SVG data values for the icon/brand mixins.",
255
+ "tokens": [
256
+ "--logo-default",
257
+ "--logo-mark",
258
+ "--logo-monochrome",
259
+ "--logo-wordmark"
260
+ ]
261
+ },
262
+ "touch-target": {
263
+ "enables": "Minimum interactive target size read by form and button mixins (WCAG 2.2 SC 2.5.8); the library default is 24px.",
264
+ "tokens": [
265
+ "--touch-target-min"
266
+ ]
267
+ }
268
+ }
176
269
  }