@marianmeres/design-tokens 1.3.0 → 1.4.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.
Files changed (55) hide show
  1. package/AGENTS.md +65 -34
  2. package/API.md +205 -44
  3. package/README.md +31 -0
  4. package/dist/css/amber-olive-safari.css +24 -50
  5. package/dist/css/blue-orange.css +24 -50
  6. package/dist/css/cyan-red.css +24 -50
  7. package/dist/css/cyan-slate.css +24 -50
  8. package/dist/css/emerald-amber-forest.css +24 -50
  9. package/dist/css/emerald-pink.css +24 -50
  10. package/dist/css/fuchsia-emerald.css +24 -50
  11. package/dist/css/gray.css +24 -50
  12. package/dist/css/indigo-amber.css +24 -50
  13. package/dist/css/lime-fuchsia-neon.css +24 -50
  14. package/dist/css/mauve-lime-electric.css +24 -50
  15. package/dist/css/mauve-teal.css +24 -50
  16. package/dist/css/mauve.css +24 -50
  17. package/dist/css/mist-indigo-fjord.css +24 -50
  18. package/dist/css/mist-violet-aurora.css +24 -50
  19. package/dist/css/mist.css +24 -50
  20. package/dist/css/monokai-cyan.css +24 -50
  21. package/dist/css/monokai-green.css +24 -50
  22. package/dist/css/monokai-pink.css +24 -50
  23. package/dist/css/olive-amber-safari.css +24 -50
  24. package/dist/css/olive.css +24 -50
  25. package/dist/css/orange-pink-sunset.css +24 -50
  26. package/dist/css/pink-emerald.css +24 -50
  27. package/dist/css/pink-teal.css +24 -50
  28. package/dist/css/purple-yellow.css +24 -50
  29. package/dist/css/rainbow.css +24 -50
  30. package/dist/css/red-blue.css +24 -50
  31. package/dist/css/red-cyan.css +24 -50
  32. package/dist/css/red-sky-slate.css +24 -50
  33. package/dist/css/red-sky.css +24 -50
  34. package/dist/css/rose-teal.css +24 -50
  35. package/dist/css/sky-amber.css +24 -50
  36. package/dist/css/slate-cyan.css +24 -50
  37. package/dist/css/slate-teal-ocean.css +24 -50
  38. package/dist/css/stone-orange-earth.css +24 -50
  39. package/dist/css/stone.css +24 -50
  40. package/dist/css/taupe-olive-clay.css +24 -50
  41. package/dist/css/taupe-rose-blush.css +24 -50
  42. package/dist/css/taupe.css +24 -50
  43. package/dist/css/teal-rose.css +24 -50
  44. package/dist/css/violet-lime.css +24 -50
  45. package/dist/css/violet-rose-dusk.css +24 -50
  46. package/dist/css/zinc.css +24 -50
  47. package/dist/generate.d.ts +56 -7
  48. package/dist/generate.js +91 -39
  49. package/dist/mod.d.ts +9 -1
  50. package/dist/reboot-bridge.d.ts +20 -5
  51. package/dist/reboot-bridge.js +45 -12
  52. package/dist/themes/mod.d.ts +7 -0
  53. package/dist/themes/mod.js +95 -0
  54. package/dist/types.d.ts +20 -4
  55. package/package.json +10 -1
package/AGENTS.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @marianmeres/design-tokens — Agent Guide
2
2
 
3
3
  ## Quick Reference
4
+
4
5
  - **Stack**: Deno / TypeScript
5
6
  - **Run**: `deno task test` | **Watch**: `deno task test:watch`
6
7
  - **Build (npm)**: `deno task npm:build`
7
- - **Build (CSS)**: `deno task css:build` (generates pre-built CSS files + updates deno.json exports)
8
+ - **Build (CSS)**: `deno task css:build` (generates pre-built CSS files; accepts `CSS_PREFIX` env var, default `"stuic-"`)
9
+ - **Build (themes barrel)**: `deno task themes:build` (regenerates `src/themes/mod.ts`)
8
10
  - **Build (preview)**: `deno task preview:build`
9
11
  - **Publish**: `deno task publish` (JSR + npm)
10
12
  - **Format**: `deno fmt` (tabs, 90 chars, indent 4)
@@ -16,10 +18,11 @@ CSS custom property generator for design token systems. Takes a structured
16
18
  declarations with automatic hover/active state derivation via `color-mix()`.
17
19
 
18
20
  Entry points:
21
+
19
22
  - `.` — core generation functions, types, color palette
20
23
  - `./reboot` — Bootstrap Reboot `--bs-*` variable bridge
21
- - `./themes` — 42 pre-built theme definitions
22
- - `./css/*.css` — pre-built CSS files with `stuic-` prefix (npm only, not on JSR)
24
+ - `./themes` — 43 pre-built theme definitions + `themeNames` constant + `bundledThemes` / `bundledThemeNames` / `getBundledTheme` runtime helpers
25
+ - `./css/*.css` — pre-built CSS files (**npm only** — JSR does not support directory exports)
23
26
 
24
27
  ## Project Structure
25
28
 
@@ -29,64 +32,85 @@ Entry points:
29
32
  types.ts — core type definitions (ThemeSchema, TokenSchema, etc.)
30
33
  generate.ts — token generation + CSS output (generateCssTokens, toCssString, generateThemeCss)
31
34
  utils.ts — hex color parsing (hexToRgb, hexToRgbTriplet)
32
- colors.ts — Tailwind color palette (v3 + v4.2 additions: taupe, mauve, mist, olive)
35
+ colors.ts — Tailwind color palette (v3 + v4.2 additions: taupe, mauve, mist, olive), as const
33
36
  reboot-bridge.ts — Bootstrap Reboot --bs-* variable mapping
34
37
  /themes
35
- mod.ts — re-exports all theme definitions
38
+ mod.ts — auto-generated barrel + runtime registry (via scripts/build-themes-mod.ts)
36
39
  *.ts — individual theme files (default export: ThemeSchema)
37
- /css — pre-built CSS files (generated by scripts/build-css.ts)
40
+ /css — pre-built CSS files (generated by scripts/build-css.ts; filenames = kebab-case theme names)
38
41
  /tests — test files (deno test --allow-read)
39
42
  /scripts
40
43
  build-npm.ts — npm package build (also copies css/ into dist)
41
- build-css.ts — generates css/*.css from all themes with "stuic-" prefix
44
+ build-css.ts — generates css/*.css from all themes; honors CSS_PREFIX env var
45
+ build-themes-mod.ts — auto-generates src/themes/mod.ts + inline registry helpers
42
46
  /theme-preview-builder — HTML preview tool
47
+ /mcp.ts — MCP tool definitions (generate-theme-css, preview-bundled-theme)
43
48
  ```
44
49
 
45
50
  ## Public API Surface
46
51
 
47
52
  ### Functions (from `./src/mod.ts`)
48
- | Function | Purpose |
49
- |----------|---------|
50
- | `generateCssTokens(schema, prefix, mode?)` | Generate token key-value pairs from a TokenSchema |
51
- | `toCssString(tokens, selector?)` | Convert tokens to a CSS declaration block |
52
- | `generateThemeCss(schema, prefix)` | Generate complete CSS for light + optional dark mode |
53
- | `hexToRgb(hex)` | Parse hex to `[r, g, b]` tuple |
54
- | `hexToRgbTriplet(hex)` | Convert hex to `"r, g, b"` string |
53
+
54
+ | Function | Purpose |
55
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------- |
56
+ | `generateCssTokens(schema, prefix, options?)` | Generate token key-value pairs. Third arg: `GenerateOptions` object or `"light"`/`"dark"` shorthand |
57
+ | `toCssString(tokens, selector?)` | Convert tokens to a CSS declaration block (groups by base color name) |
58
+ | `generateThemeCss(schema, prefix, options?)` | Generate complete CSS for light + optional dark mode; supports `cssLayer` wrapping |
59
+ | `hexToRgb(hex)` | Parse hex to `[r, g, b]` tuple |
60
+ | `hexToRgbTriplet(hex)` | Convert hex to `"r, g, b"` string |
55
61
 
56
62
  ### Functions (from `./reboot`)
57
- | Function | Purpose |
58
- |----------|---------|
59
- | `generateRebootBridge(tokens, prefix)` | Map tokens to `--bs-*` variables |
60
- | `generateThemedCss(schema, prefix)` | Combined tokens + reboot bridge CSS |
63
+
64
+ | Function | Purpose |
65
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
66
+ | `generateRebootBridge(tokens, prefix)` | Map tokens to `--bs-*` variables. `-rgb` companions only emitted for literal hex values — auto-derived `color-mix()` values cannot produce triplets |
67
+ | `generateThemedCss(schema, prefix, options?)` | Combined tokens + reboot bridge CSS |
68
+
69
+ ### Functions (from `./themes`)
70
+
71
+ | Function | Purpose |
72
+ | ----------------------- | --------------------------------------------------------------------------- |
73
+ | `getBundledTheme(name)` | Look up bundled theme by camelCase name; returns `ThemeSchema \| undefined` |
61
74
 
62
75
  ### Key Types
63
- | Type | Purpose |
64
- |------|---------|
65
- | `ThemeSchema` | `{ light: TokenSchema; dark?: TokenSchema }` |
66
- | `TokenSchema` | Intent colors, role paired/single colors |
67
- | `ColorPair` | `{ DEFAULT, foreground, hover?, active? }` |
68
- | `ColorValue` | `{ DEFAULT, hover?, active? }` |
69
- | `SingleColor` | `string \| ColorValue` |
70
- | `GeneratedTokens` | `Record<string, string>` |
76
+
77
+ | Type | Purpose |
78
+ | ----------------------- | ------------------------------------------------------------------------------- |
79
+ | `ThemeSchema` | `{ light: TokenSchema; dark?: TokenSchema }` |
80
+ | `TokenSchema` | Intent colors, role paired/single colors |
81
+ | `ColorPair` | `{ DEFAULT, foreground, hover?, active?, foregroundHover?, foregroundActive? }` |
82
+ | `ColorValue` | `{ DEFAULT, hover?, active? }` |
83
+ | `SingleColor` | `string \| ColorValue` |
84
+ | `GeneratedTokens` | `Record<string, string>` |
85
+ | `GenerateOptions` | `{ mode?, deriveStates?, surfaceForegroundContrast? }` |
86
+ | `GenerateThemeOptions` | `Omit<GenerateOptions, "mode"> & { cssLayer? }` |
87
+ | `RolePairedKey` | Required: `"background" \| "muted" \| "surface"` |
88
+ | `RolePairedOptionalKey` | Optional: `"surface-1"` (all bundled themes ship it) |
71
89
 
72
90
  ### Constants
73
- | Export | Purpose |
74
- |--------|---------|
75
- | `colors` | Tailwind hex palette including v4.2 additions (`colors.blue[600]`, `colors.mauve[500]`) |
91
+
92
+ | Export | Purpose |
93
+ | ------------------- | ---------------------------------------------------------------- |
94
+ | `colors` | Tailwind hex palette (with `as const`, full literal types) |
95
+ | `themeNames` | Kebab-case theme names (readonly tuple) |
96
+ | `bundledThemes` | Map of camelCase name → ThemeSchema (no leaky non-theme exports) |
97
+ | `bundledThemeNames` | Array of camelCase theme names |
76
98
 
77
99
  ## Critical Conventions
78
100
 
79
101
  1. All public functions must have JSDoc and explicit return types (Deno/JSR requirement)
80
102
  2. Token naming: `--{prefix}color-{key}`, `--{prefix}color-{key}-hover`, etc.
81
- 3. Hover/active auto-derived via `color-mix(in oklch, ...)` — light darkens with black, dark lightens with white
82
- 4. Surface-intent tokens derived via `color-mix(in srgb, ...)` from intent + background
83
- 5. Theme files use `export default` with `ThemeSchema` type annotation
84
- 6. Formatting: tabs, 90-char line width, 4-space indent width (`deno fmt`)
103
+ 3. Prefix is auto-normalized — `"my"` and `"my-"` both produce `--my-color-*`. Empty string produces unprefixed.
104
+ 4. Hover/active auto-derived via `color-mix(in oklch, ...)` — light darkens with black, dark lightens with white. Disable with `deriveStates: false`.
105
+ 5. Surface-intent tokens derived via `color-mix(in srgb, ...)` from intent + background. Foreground contrast defaults to 50% — tune via `surfaceForegroundContrast`.
106
+ 6. Theme files use `export default` with `ThemeSchema` type annotation. All bundled themes include an optional `surface-1` paired role (extra elevation).
107
+ 7. Formatting: tabs, 90-char line width, 4-space indent width (`deno fmt`)
108
+ 8. MCP handlers must return `Promise<string>` — framework contract from `jsr:@marianmeres/mcp-server/types`.
85
109
 
86
110
  ## Adding a New Theme
87
111
 
88
112
  1. Create `src/themes/{name}.ts` with `export default` of `ThemeSchema`
89
- 2. Run `deno task themes:build` to regenerate `src/themes/mod.ts`
113
+ 2. Run `deno task themes:build` to regenerate `src/themes/mod.ts` (barrel + registry)
90
114
  3. Run `deno task css:build` to regenerate CSS files
91
115
  4. Run `deno task preview:build` to rebuild the preview tool
92
116
  5. If the theme uses new color families, update `COLOR_FAMILIES` in `theme-preview-builder/index.html`
@@ -95,5 +119,12 @@ Entry points:
95
119
 
96
120
  - [ ] Read existing patterns in similar files
97
121
  - [ ] Run `deno task test` — all tests must pass
122
+ - [ ] `deno check src/mod.ts src/themes/mod.ts src/reboot-bridge.ts mcp.ts scripts/*.ts` — typecheck
98
123
  - [ ] Follow `deno fmt` conventions
99
124
  - [ ] Ensure new exports have JSDoc + explicit return types
125
+ - [ ] If changing generator output (CSS text), note as potential BC change
126
+
127
+ ## Known Limitations
128
+
129
+ - **JSR does not support directory exports** → pre-built CSS is npm-only.
130
+ - **Reboot `-rgb` companions** are omitted for tokens whose values are `color-mix()` expressions (auto-derived hovers). Provide explicit hex `hover` / `active` if you need full alpha-blend support through Reboot's `rgba(var(--bs-…-rgb), α)` rules.
package/API.md CHANGED
@@ -2,38 +2,53 @@
2
2
 
3
3
  ## Functions
4
4
 
5
- ### `generateCssTokens(schema, prefix, mode?)`
5
+ ### `generateCssTokens(schema, prefix, options?)`
6
6
 
7
7
  Generate CSS token key-value pairs from a single-mode token schema.
8
8
 
9
9
  **Parameters:**
10
+
10
11
  - `schema` (`TokenSchema`) — Color definitions for intent, role paired, and role single colors
11
- - `prefix` (`string`) — CSS variable prefix (e.g. `"my-"` produces `--my-color-primary`)
12
- - `mode` (`"light" | "dark"`, optional) — Affects auto-derivation direction. Light darkens with black, dark lightens with white. Default: `"light"`
12
+ - `prefix` (`string`) — CSS variable prefix. Auto-normalized: `"my"` and `"my-"` are equivalent. `""` produces unprefixed tokens.
13
+ - `options` (`GenerateOptions | "light" | "dark"`, optional) — A mode string for quick switching, or an options object. Default: `{}`
14
+ - `mode` (`"light" | "dark"`, optional) — Affects auto-derivation direction. Light darkens with black, dark lightens with white. Default: `"light"`
15
+ - `deriveStates` (`boolean`, optional) — When `false`, missing hover/active fall back to DEFAULT instead of being derived via `color-mix()`. Useful for environments without `color-mix()` support. Default: `true`
16
+ - `surfaceForegroundContrast` (`number`, optional) — Mix percentage (0–100) used to derive the `surface-{intent}-foreground` token. Higher values increase contrast against the tinted surface background. Default: `50`
13
17
 
14
18
  **Returns:** `GeneratedTokens` — Key-value record of token names to CSS values (keys without the `--` prefix)
15
19
 
16
20
  **Example:**
21
+
17
22
  ```typescript
18
23
  import { generateCssTokens } from "@marianmeres/design-tokens";
19
24
 
20
- const tokens = generateCssTokens(schema.light, "app-", "light");
21
- // { "app-color-primary": "#2563eb", "app-color-primary-hover": "color-mix(...)", ... }
25
+ // Shorthand mode string
26
+ const tokens = generateCssTokens(schema.light, "app-", "dark");
27
+
28
+ // Full options object
29
+ const flat = generateCssTokens(schema.light, "app", {
30
+ mode: "light",
31
+ deriveStates: false,
32
+ surfaceForegroundContrast: 70,
33
+ });
34
+ // { "app-color-primary": "#2563eb", "app-color-primary-hover": "#2563eb", ... }
22
35
  ```
23
36
 
24
37
  ---
25
38
 
26
39
  ### `toCssString(tokens, selector?)`
27
40
 
28
- Convert a tokens record to a formatted CSS declaration block, grouped by base color name.
41
+ Convert a tokens record to a formatted CSS declaration block, grouped by base color name (e.g. all `surface-*` tokens cluster together).
29
42
 
30
43
  **Parameters:**
44
+
31
45
  - `tokens` (`GeneratedTokens`) — Key-value token pairs (from `generateCssTokens`)
32
46
  - `selector` (`string`, optional) — CSS selector for the block. Default: `":root"`
33
47
 
34
48
  **Returns:** `string` — Formatted CSS string
35
49
 
36
50
  **Example:**
51
+
37
52
  ```typescript
38
53
  import { generateCssTokens, toCssString } from "@marianmeres/design-tokens";
39
54
 
@@ -46,22 +61,30 @@ const css = toCssString(tokens, ":root.dark");
46
61
 
47
62
  ---
48
63
 
49
- ### `generateThemeCss(schema, prefix)`
64
+ ### `generateThemeCss(schema, prefix, options?)`
50
65
 
51
66
  Generate complete CSS for a theme with light mode and optional dark mode.
52
67
 
53
68
  **Parameters:**
69
+
54
70
  - `schema` (`ThemeSchema`) — Theme with required `light` and optional `dark` token schemas
55
- - `prefix` (`string`) — CSS variable prefix
71
+ - `prefix` (`string`) — CSS variable prefix (trailing dash optional)
72
+ - `options` (`GenerateThemeOptions`, optional) — Accepts all `GenerateOptions` (except `mode`, which is set per section) plus:
73
+ - `cssLayer` (`string`, optional) — Wrap the output in `@layer {name} { ... }`. Useful for CSS cascade control.
56
74
 
57
- **Returns:** `string` — Complete CSS with `:root` for light and `:root.dark` for dark mode
75
+ **Returns:** `string` — Complete CSS with `:root` for light and `:root.dark` for dark mode, optionally wrapped in `@layer`.
58
76
 
59
77
  **Example:**
78
+
60
79
  ```typescript
61
80
  import { generateThemeCss } from "@marianmeres/design-tokens";
62
81
  import { zinc } from "@marianmeres/design-tokens/themes";
63
82
 
64
- const css = generateThemeCss(zinc, "app-");
83
+ const css = generateThemeCss(zinc, "app-", { cssLayer: "tokens" });
84
+ // @layer tokens {
85
+ // :root { ... }
86
+ // :root.dark { ... }
87
+ // }
65
88
  ```
66
89
 
67
90
  ---
@@ -71,17 +94,19 @@ const css = generateThemeCss(zinc, "app-");
71
94
  Parse a hex color string to RGB components. Supports 3-digit (`#abc`) and 6-digit (`#aabbcc`) formats, with or without `#` prefix.
72
95
 
73
96
  **Parameters:**
97
+
74
98
  - `hex` (`string`) — Hex color string
75
99
 
76
100
  **Returns:** `[number, number, number] | null` — RGB tuple, or `null` if invalid
77
101
 
78
102
  **Example:**
103
+
79
104
  ```typescript
80
105
  import { hexToRgb } from "@marianmeres/design-tokens";
81
106
 
82
107
  hexToRgb("#2563eb"); // → [37, 99, 235]
83
- hexToRgb("fff"); // → [255, 255, 255]
84
- hexToRgb("nope"); // → null
108
+ hexToRgb("fff"); // → [255, 255, 255]
109
+ hexToRgb("nope"); // → null
85
110
  ```
86
111
 
87
112
  ---
@@ -91,11 +116,13 @@ hexToRgb("nope"); // → null
91
116
  Convert a hex color to an `"r, g, b"` triplet string suitable for CSS `rgb()` usage.
92
117
 
93
118
  **Parameters:**
119
+
94
120
  - `hex` (`string`) — Hex color string
95
121
 
96
122
  **Returns:** `string | null` — Comma-separated RGB triplet, or `null` if invalid
97
123
 
98
124
  **Example:**
125
+
99
126
  ```typescript
100
127
  import { hexToRgbTriplet } from "@marianmeres/design-tokens";
101
128
 
@@ -106,31 +133,37 @@ hexToRgbTriplet("#2563eb"); // → "37, 99, 235"
106
133
 
107
134
  ### `generateRebootBridge(tokens, prefix)`
108
135
 
109
- *Import from `@marianmeres/design-tokens/reboot`*
136
+ _Import from `@marianmeres/design-tokens/reboot`_
110
137
 
111
138
  Map design tokens to Bootstrap Reboot `--bs-*` CSS variables. Automatically generates `-rgb` triplet companions for hex color values.
112
139
 
113
140
  **Parameters:**
141
+
114
142
  - `tokens` (`GeneratedTokens`) — Generated token pairs (from `generateCssTokens`)
115
- - `prefix` (`string`) — The same prefix used in `generateCssTokens`
143
+ - `prefix` (`string`) — The same prefix used in `generateCssTokens` (trailing dash optional)
116
144
 
117
145
  **Returns:** `GeneratedTokens` — Bootstrap bridge variables (`bs-body-bg`, `bs-link-color`, etc.)
118
146
 
147
+ **Note on `-rgb` companions:** Bootstrap's alpha-blended rules (e.g. `rgba(var(--bs-link-color-rgb), 0.5)`) require raw RGB triplets. This bridge emits `--bs-*-rgb` only when the underlying token resolves to a literal hex color. Auto-derived hover/active tokens are `color-mix()` expressions and cannot produce triplets — the companions are omitted, and Reboot rules that rely on them fall back to defaults. Provide explicit hex `hover` / `active` values on your `ColorPair`s if you need full alpha-blend support.
148
+
119
149
  ---
120
150
 
121
- ### `generateThemedCss(schema, prefix)`
151
+ ### `generateThemedCss(schema, prefix, options?)`
122
152
 
123
- *Import from `@marianmeres/design-tokens/reboot`*
153
+ _Import from `@marianmeres/design-tokens/reboot`_
124
154
 
125
155
  Generate complete themed CSS including both design tokens AND the Bootstrap Reboot bridge.
126
156
 
127
157
  **Parameters:**
158
+
128
159
  - `schema` (`ThemeSchema`) — Theme schema with light and optional dark modes
129
- - `prefix` (`string`) — CSS variable prefix
160
+ - `prefix` (`string`) — CSS variable prefix (trailing dash optional)
161
+ - `options` (`Omit<GenerateThemeOptions, "cssLayer">`, optional) — Forwarded to `generateCssTokens` for each mode (see `generateCssTokens`)
130
162
 
131
163
  **Returns:** `string` — Combined CSS with token variables and `--bs-*` bridge variables
132
164
 
133
165
  **Example:**
166
+
134
167
  ```typescript
135
168
  import { generateThemedCss } from "@marianmeres/design-tokens/reboot";
136
169
  import { zinc } from "@marianmeres/design-tokens/themes";
@@ -141,14 +174,37 @@ const css = generateThemedCss(zinc, "app-");
141
174
 
142
175
  ---
143
176
 
177
+ ### `getBundledTheme(name)`
178
+
179
+ _Import from `@marianmeres/design-tokens/themes`_
180
+
181
+ Look up a bundled theme by its camelCase name. Returns `undefined` if the name is not a known theme.
182
+
183
+ **Parameters:**
184
+
185
+ - `name` (`string`) — Theme name (camelCase, e.g. `"blueOrange"`)
186
+
187
+ **Returns:** `ThemeSchema | undefined`
188
+
189
+ **Example:**
190
+
191
+ ```typescript
192
+ import { getBundledTheme } from "@marianmeres/design-tokens/themes";
193
+
194
+ const theme = getBundledTheme("mauveTeal");
195
+ if (theme) { /* use theme */ }
196
+ ```
197
+
198
+ ---
199
+
144
200
  ## Types
145
201
 
146
202
  ### `ThemeSchema`
147
203
 
148
204
  ```typescript
149
205
  type ThemeSchema = {
150
- light: TokenSchema;
151
- dark?: TokenSchema;
206
+ light: TokenSchema;
207
+ dark?: TokenSchema;
152
208
  };
153
209
  ```
154
210
 
@@ -160,17 +216,17 @@ Complete theme definition with required light mode and optional dark mode.
160
216
 
161
217
  ```typescript
162
218
  type TokenSchema = {
163
- colors: {
164
- intent: WithRequired<IntentColorKey, ColorPair>;
165
- role: {
166
- paired: WithRequired<RolePairedKey, ColorPair>;
167
- single: WithRequired<RoleSingleKey, SingleColor>;
168
- };
169
- };
219
+ colors: {
220
+ intent: WithRequired<IntentColorKey, ColorPair>;
221
+ role: {
222
+ paired: WithKnown<RolePairedKey, RolePairedOptionalKey, ColorPair>;
223
+ single: WithRequired<RoleSingleKey, SingleColor>;
224
+ };
225
+ };
170
226
  };
171
227
  ```
172
228
 
173
- Single-mode token schema. Intent colors define primary UI actions; role colors define structural UI elements.
229
+ Single-mode token schema. Intent colors define primary UI actions; role colors define structural UI elements. Arbitrary string keys are allowed on every collection (for custom additions).
174
230
 
175
231
  ---
176
232
 
@@ -178,14 +234,16 @@ Single-mode token schema. Intent colors define primary UI actions; role colors d
178
234
 
179
235
  ```typescript
180
236
  type ColorPair = {
181
- DEFAULT: string;
182
- foreground: string;
183
- hover?: string;
184
- active?: string;
237
+ DEFAULT: string;
238
+ foreground: string;
239
+ hover?: string;
240
+ active?: string;
241
+ foregroundHover?: string;
242
+ foregroundActive?: string;
185
243
  };
186
244
  ```
187
245
 
188
- Color with a foreground companion. Hover/active are auto-derived when omitted.
246
+ Color with a foreground companion. Hover/active are auto-derived when omitted. `foregroundHover` and `foregroundActive` default to `foreground` — provide them if your foreground should change on interaction.
189
247
 
190
248
  ---
191
249
 
@@ -193,9 +251,9 @@ Color with a foreground companion. Hover/active are auto-derived when omitted.
193
251
 
194
252
  ```typescript
195
253
  type ColorValue = {
196
- DEFAULT: string;
197
- hover?: string;
198
- active?: string;
254
+ DEFAULT: string;
255
+ hover?: string;
256
+ active?: string;
199
257
  };
200
258
  ```
201
259
 
@@ -213,6 +271,32 @@ Either a plain CSS color string or an object with optional hover/active states.
213
271
 
214
272
  ---
215
273
 
274
+ ### `GenerateOptions`
275
+
276
+ ```typescript
277
+ type GenerateOptions = {
278
+ mode?: "light" | "dark";
279
+ deriveStates?: boolean;
280
+ surfaceForegroundContrast?: number;
281
+ };
282
+ ```
283
+
284
+ Options passed to `generateCssTokens`. See the function docs above for field meanings.
285
+
286
+ ---
287
+
288
+ ### `GenerateThemeOptions`
289
+
290
+ ```typescript
291
+ type GenerateThemeOptions = Omit<GenerateOptions, "mode"> & {
292
+ cssLayer?: string;
293
+ };
294
+ ```
295
+
296
+ Options passed to `generateThemeCss`. Omits `mode` (set automatically per section) and adds `cssLayer` for optional `@layer` wrapping.
297
+
298
+ ---
299
+
216
300
  ### `GeneratedTokens`
217
301
 
218
302
  ```typescript
@@ -237,6 +321,18 @@ type IntentColorKey = "primary" | "accent" | "destructive" | "warning" | "succes
237
321
  type RolePairedKey = "background" | "muted" | "surface";
238
322
  ```
239
323
 
324
+ Required paired role keys.
325
+
326
+ ---
327
+
328
+ ### `RolePairedOptionalKey`
329
+
330
+ ```typescript
331
+ type RolePairedOptionalKey = "surface-1";
332
+ ```
333
+
334
+ Optional paired role keys. Conventional but not required — all bundled themes define `surface-1` as an additional elevation layer. Additional arbitrary string keys are also allowed.
335
+
240
336
  ---
241
337
 
242
338
  ### `RoleSingleKey`
@@ -247,6 +343,27 @@ type RoleSingleKey = "foreground" | "border" | "input" | "ring";
247
343
 
248
344
  ---
249
345
 
346
+ ### `WithRequired` / `WithOptional` / `WithKnown`
347
+
348
+ Helper types for schema collections:
349
+
350
+ ```typescript
351
+ type WithRequired<K extends string, V> =
352
+ & Record<K, V>
353
+ & Record<string, V>;
354
+
355
+ type WithOptional<K extends string, V> =
356
+ & Partial<Record<K, V>>
357
+ & Record<string, V>;
358
+
359
+ type WithKnown<Req extends string, Opt extends string, V> =
360
+ & Record<Req, V>
361
+ & Partial<Record<Opt, V>>
362
+ & Record<string, V>;
363
+ ```
364
+
365
+ ---
366
+
250
367
  ## Constants
251
368
 
252
369
  ### `colors`
@@ -262,31 +379,75 @@ Tailwind CSS color palette as hex values. Contains `black`, `white`, and 26 colo
262
379
  ```typescript
263
380
  import { colors } from "@marianmeres/design-tokens";
264
381
 
265
- colors.blue[600] // → "#2563eb"
266
- colors.mauve[500] // → "#79697b"
267
- colors.white // → "#ffffff"
382
+ colors.blue[600]; // → "#2563eb"
383
+ colors.mauve[500]; // → "#79697b"
384
+ colors.white; // → "#ffffff"
385
+ ```
386
+
387
+ ---
388
+
389
+ ### `bundledThemes`
390
+
391
+ _Import from `@marianmeres/design-tokens/themes`_
392
+
393
+ Map of all bundled themes keyed by their camelCase name. Safe to iterate — the non-theme exports (`themeNames`, `ThemeSchema`) are filtered out.
394
+
395
+ ```typescript
396
+ import { bundledThemes } from "@marianmeres/design-tokens/themes";
397
+
398
+ Object.keys(bundledThemes); // → ["amberOliveSafari", "blueOrange", ...]
399
+ ```
400
+
401
+ ---
402
+
403
+ ### `bundledThemeNames`
404
+
405
+ _Import from `@marianmeres/design-tokens/themes`_
406
+
407
+ Array of all bundled theme names (camelCase), discovered at runtime from `bundledThemes`.
408
+
409
+ ---
410
+
411
+ ### `themeNames`
412
+
413
+ _Import from `@marianmeres/design-tokens/themes`_
414
+
415
+ Kebab-case theme name strings matching the generated CSS filenames (without extension). Typed as a `readonly` tuple.
416
+
417
+ ```typescript
418
+ import { themeNames } from "@marianmeres/design-tokens/themes";
419
+
420
+ themeNames; // → ["amber-olive-safari", "blue-orange", ..., "zinc"]
268
421
  ```
269
422
 
270
423
  ---
271
424
 
272
425
  ## Bundled Themes
273
426
 
274
- *Import from `@marianmeres/design-tokens/themes`*
427
+ _Import from `@marianmeres/design-tokens/themes`_
428
+
429
+ 43 pre-built `ThemeSchema` definitions, each with light + dark mode:
275
430
 
276
- 42 pre-built `ThemeSchema` definitions, each with light + dark mode:
431
+ `amberOliveSafari`, `blueOrange`, `cyanRed`, `cyanSlate`, `emeraldAmberForest`, `emeraldPink`, `fuchsiaEmerald`, `gray`, `indigoAmber`, `limeFuchsiaNeon`, `mauve`, `mauveLimeElectric`, `mauveTeal`, `mist`, `mistIndigoFjord`, `mistVioletAurora`, `monokaiCyan`, `monokaiGreen`, `monokaiPink`, `olive`, `oliveAmberSafari`, `orangePinkSunset`, `pinkEmerald`, `pinkTeal`, `purpleYellow`, `rainbow`, `redBlue`, `redCyan`, `redSky`, `redSkySlate`, `roseTeal`, `skyAmber`, `slateCyan`, `slateTealOcean`, `stone`, `stoneOrangeEarth`, `taupe`, `taupeOliveClay`, `taupeRoseBlush`, `tealRose`, `violetLime`, `violetRoseDusk`, `zinc`
277
432
 
278
- `blueOrange`, `cyanRed`, `cyanSlate`, `emeraldAmberForest`, `emeraldPink`, `fuchsiaEmerald`, `gray`, `indigoAmber`, `limeFuchsiaNeon`, `mauve`, `mauveLimeElectric`, `mauveTeal`, `mist`, `mistIndigoFjord`, `mistVioletAurora`, `monokaiCyan`, `monokaiGreen`, `monokaiPink`, `olive`, `oliveAmberSafari`, `orangePinkSunset`, `pinkEmerald`, `pinkTeal`, `purpleYellow`, `rainbow`, `redBlue`, `redCyan`, `redSky`, `redSkySlate`, `roseTeal`, `skyAmber`, `slateCyan`, `slateTealOcean`, `stone`, `taupe`, `taupeOliveClay`, `taupeRoseBlush`, `stoneOrangeEarth`, `tealRose`, `violetLime`, `violetRoseDusk`, `zinc`
433
+ All bundled themes include a `surface-1` paired role (on top of the required `background`, `muted`, `surface`) as a conventional extra elevation.
279
434
 
280
435
  ---
281
436
 
282
437
  ## Pre-built CSS
283
438
 
284
- *npm only — not available on JSR*
439
+ _npm only — JSR does not support directory exports, so the pre-built CSS files are not available via JSR._
285
440
 
286
- All bundled themes are also distributed as pre-built CSS files using the `stuic-` prefix. Import via `@marianmeres/design-tokens/css/{theme-name}.css`.
441
+ All bundled themes are also distributed as pre-built CSS files. Import via `@marianmeres/design-tokens/css/{theme-name}.css`.
287
442
 
288
443
  ```typescript
289
444
  import "@marianmeres/design-tokens/css/mauve-teal.css";
290
445
  ```
291
446
 
292
- Available files: `blue-orange.css`, `cyan-red.css`, `mauve-teal.css`, `mist-indigo-fjord.css`, `taupe-rose-blush.css`, etc. (one per bundled theme)
447
+ The default prefix on the published files is `stuic-` (i.e. tokens are named `--stuic-color-primary`, etc.). To regenerate the CSS files locally with a different prefix, set the `CSS_PREFIX` environment variable before running the build:
448
+
449
+ ```bash
450
+ CSS_PREFIX=app- deno task css:build
451
+ ```
452
+
453
+ Available files: `amber-olive-safari.css`, `blue-orange.css`, `cyan-red.css`, `mauve-teal.css`, `mist-indigo-fjord.css`, `taupe-rose-blush.css`, etc. (one per bundled theme).
package/README.md CHANGED
@@ -37,6 +37,8 @@ const theme: ThemeSchema = {
37
37
  background: { DEFAULT: "#ffffff", foreground: "#171717" },
38
38
  muted: { DEFAULT: "#f5f5f5", foreground: "#737373" },
39
39
  surface: { DEFAULT: "#e5e5e5", foreground: "#171717" },
40
+ // Optional extra elevation (all bundled themes include it):
41
+ "surface-1": { DEFAULT: "#d4d4d4", foreground: "#171717" },
40
42
  },
41
43
  single: {
42
44
  foreground: "#171717",
@@ -49,6 +51,7 @@ const theme: ThemeSchema = {
49
51
  },
50
52
  };
51
53
 
54
+ // The prefix's trailing dash is optional — "my" and "my-" are equivalent.
52
55
  const css = generateThemeCss(theme, "my-");
53
56
  // → :root { --my-color-primary: #2563eb; ... }
54
57
  ```
@@ -62,6 +65,7 @@ All bundled themes are available as pre-built CSS files with the `stuic-` prefix
62
65
  ```
63
66
 
64
67
  Or via bundler import:
68
+
65
69
  ```ts
66
70
  import "@marianmeres/design-tokens/css/mauve-teal.css";
67
71
  ```
@@ -98,6 +102,10 @@ const myTheme: ThemeSchema = {
98
102
  DEFAULT: colors.slate[200],
99
103
  foreground: colors.slate[900],
100
104
  },
105
+ "surface-1": {
106
+ DEFAULT: colors.slate[300],
107
+ foreground: colors.slate[900],
108
+ },
101
109
  },
102
110
  single: {
103
111
  foreground: colors.slate[900],
@@ -126,6 +134,29 @@ import { generateThemeCss } from "@marianmeres/design-tokens";
126
134
  const css = generateThemeCss(zinc, "app-");
127
135
  ```
128
136
 
137
+ Dynamic lookup via the registry (useful when the theme name comes from config):
138
+
139
+ ```ts
140
+ import { bundledThemeNames, getBundledTheme } from "@marianmeres/design-tokens/themes";
141
+
142
+ const theme = getBundledTheme("mauveTeal");
143
+ if (theme) {
144
+ const css = generateThemeCss(theme, "app-");
145
+ }
146
+ ```
147
+
148
+ ### Options
149
+
150
+ `generateThemeCss` and `generateCssTokens` accept an options object:
151
+
152
+ ```ts
153
+ generateThemeCss(zinc, "app-", {
154
+ deriveStates: false, // disable color-mix hover/active derivation
155
+ surfaceForegroundContrast: 70, // stronger contrast on surface-{intent}-foreground
156
+ cssLayer: "tokens", // wrap output in @layer tokens { ... }
157
+ });
158
+ ```
159
+
129
160
  ## Reboot bridge
130
161
 
131
162
  Maps design tokens to Bootstrap Reboot's `--bs-*` CSS variables: