@phcdevworks/spectre-tokens 2.9.0 → 3.1.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/README.md +95 -58
- package/dist/index.cjs +31 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +20 -2
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/dist/tokens.dtcg.json +20 -6
- package/package.json +12 -8
- package/tokens/modes.json +8 -4
- package/tokens/semantic-roles.json +5 -1
package/README.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @phcdevworks/spectre-tokens
|
|
2
2
|
|
|
3
|
+
## Repository Snapshot
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
| ---------------------- | -------------------------------- |
|
|
7
|
+
| Project team | `project-design` |
|
|
8
|
+
| Repository role | Spectre L1 design-token contract |
|
|
9
|
+
| Package/artifact | `@phcdevworks/spectre-tokens` |
|
|
10
|
+
| Current version/status | 2.9.0 |
|
|
11
|
+
|
|
12
|
+
## Standard Workflow
|
|
13
|
+
|
|
14
|
+
1. Read [AGENTS.md](AGENTS.md), then the agent-specific guide for the task.
|
|
15
|
+
2. Check [TODO.md](TODO.md) and [ROADMAP.md](ROADMAP.md) for current scope.
|
|
16
|
+
3. Make the smallest repo-local change that satisfies the task.
|
|
17
|
+
4. Run `npm run check` when validation is required or practical.
|
|
18
|
+
5. Update docs and [CHANGELOG.md](CHANGELOG.md) only when behavior, public
|
|
19
|
+
contracts, or release-relevant metadata changed.
|
|
20
|
+
|
|
21
|
+
## Documentation Map
|
|
22
|
+
|
|
23
|
+
| Guide | Path |
|
|
24
|
+
| ----------- | ---------------------------- |
|
|
25
|
+
| Agent rules | [AGENTS.md](AGENTS.md) |
|
|
26
|
+
| Claude Code | [CLAUDE.md](CLAUDE.md) |
|
|
27
|
+
| Codex | [CODEX.md](CODEX.md) |
|
|
28
|
+
| Copilot | [COPILOT.md](COPILOT.md) |
|
|
29
|
+
| Jules | [JULES.md](JULES.md) |
|
|
30
|
+
| Roadmap | [ROADMAP.md](ROADMAP.md) |
|
|
31
|
+
| Todo | [TODO.md](TODO.md) |
|
|
32
|
+
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
|
|
33
|
+
| Security | [SECURITY.md](SECURITY.md) |
|
|
34
|
+
|
|
3
35
|
[](https://www.npmjs.com/package/@phcdevworks/spectre-tokens)
|
|
4
36
|
[](https://github.com/phcdevworks/spectre-tokens/actions/workflows/ci.yml)
|
|
5
37
|
[](LICENSE)
|
|
@@ -25,13 +57,13 @@ and runtimes.
|
|
|
25
57
|
machine-readable contract authority. Everything else is derived from them or
|
|
26
58
|
validated against them.
|
|
27
59
|
|
|
28
|
-
| Layer
|
|
29
|
-
|
|
30
|
-
| Source token data
|
|
31
|
-
| Contract authority
|
|
32
|
-
| Public entry points
|
|
33
|
-
| Generated TypeScript | `src/generated/tokens.ts`
|
|
34
|
-
| Generated dist
|
|
60
|
+
| Layer | Path | Rule |
|
|
61
|
+
| -------------------- | ---------------------------------------------- | ------------------------------------------------------------------- |
|
|
62
|
+
| Source token data | `tokens/*.json` | All token value changes start here — never anywhere else |
|
|
63
|
+
| Contract authority | `contract.manifest.json` | Governs public namespaces and required output surfaces |
|
|
64
|
+
| Public entry points | `src/index.ts` · `src/types.ts` · `src/css.ts` | Contract-authority files — changes require changelog classification |
|
|
65
|
+
| Generated TypeScript | `src/generated/tokens.ts` | **Never edit directly** — regenerated by `npm run build` |
|
|
66
|
+
| Generated dist | `dist/` | **Never edit directly** — regenerated by `npm run build` |
|
|
35
67
|
|
|
36
68
|
After any source change: run `npm run build` to regenerate outputs, then
|
|
37
69
|
`npm run check` to validate the full contract.
|
|
@@ -60,18 +92,23 @@ This package is the correct place to define token meaning.
|
|
|
60
92
|
|
|
61
93
|
## When to use this package
|
|
62
94
|
|
|
63
|
-
- You are building a Spectre ecosystem package and need the visual language
|
|
64
|
-
|
|
65
|
-
- You
|
|
95
|
+
- You are building a Spectre ecosystem package and need the visual language
|
|
96
|
+
contract.
|
|
97
|
+
- You need design token values in JavaScript, TypeScript, CSS variables, or a
|
|
98
|
+
Tailwind theme.
|
|
99
|
+
- You want a single source of truth for semantic roles: `surface`, `text`,
|
|
100
|
+
`component`, `buttons`, `forms`, `modes`.
|
|
66
101
|
- You are consuming tokens as named values, not inventing new token meaning.
|
|
67
102
|
|
|
68
103
|
## When not to use this package
|
|
69
104
|
|
|
70
105
|
- You need UI components or component structure — use
|
|
71
106
|
[`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
|
|
72
|
-
- You need framework-specific component delivery — use the appropriate adapter
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
- You need framework-specific component delivery — use the appropriate adapter
|
|
108
|
+
package.
|
|
109
|
+
- You want to define your own token meaning or override Spectre semantics
|
|
110
|
+
locally — this package is the authority; downstream consumers should consume,
|
|
111
|
+
not redefine.
|
|
75
112
|
|
|
76
113
|
## Installation
|
|
77
114
|
|
|
@@ -126,15 +163,15 @@ mode-aware styling.
|
|
|
126
163
|
|
|
127
164
|
### Semantic namespaces (prefer for all UI work)
|
|
128
165
|
|
|
129
|
-
| Namespace
|
|
130
|
-
|
|
131
|
-
| `surface`
|
|
132
|
-
| `text`
|
|
166
|
+
| Namespace | What it expresses |
|
|
167
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
168
|
+
| `surface` | Background roles: page, card, input, overlay, subtle, hero (gradient, hero sections only), hover, selected, active, divider |
|
|
169
|
+
| `text` | Foreground roles: default, muted, subtle, meta, on-surface, on-page |
|
|
133
170
|
| `component` | Role-specific tokens for icon boxes, badges, ratings, testimonials, pricing cards, nav, modal, toast, tooltip, dropdown |
|
|
134
|
-
| `buttons`
|
|
135
|
-
| `forms`
|
|
136
|
-
| `link`
|
|
137
|
-
| `modes`
|
|
171
|
+
| `buttons` | Button state tokens: default, hover, active, disabled, CTA |
|
|
172
|
+
| `forms` | Form state tokens: default, focused, error, disabled |
|
|
173
|
+
| `link` | Inline link color roles: default, hover, active, visited |
|
|
174
|
+
| `modes` | Mode-aware overrides under `modes.default` and `modes.dark` |
|
|
138
175
|
|
|
139
176
|
```ts
|
|
140
177
|
import tokens from '@phcdevworks/spectre-tokens'
|
|
@@ -142,13 +179,13 @@ import tokens from '@phcdevworks/spectre-tokens'
|
|
|
142
179
|
// Semantic — always prefer this for UI
|
|
143
180
|
const card = {
|
|
144
181
|
background: tokens.surface.card,
|
|
145
|
-
color: tokens.text.onSurface.default
|
|
182
|
+
color: tokens.text.onSurface.default
|
|
146
183
|
}
|
|
147
184
|
|
|
148
185
|
// Mode-aware semantic
|
|
149
186
|
const dark = {
|
|
150
187
|
background: tokens.modes.dark.surface.page,
|
|
151
|
-
color: tokens.modes.dark.text.onPage.default
|
|
188
|
+
color: tokens.modes.dark.text.onPage.default
|
|
152
189
|
}
|
|
153
190
|
```
|
|
154
191
|
|
|
@@ -162,7 +199,7 @@ tooling that inspects palette data directly.
|
|
|
162
199
|
// Raw palette — only when fixed color access is deliberate
|
|
163
200
|
const chart = {
|
|
164
201
|
series1: tokens.colors.brand[500],
|
|
165
|
-
series2: tokens.colors.neutral[300]
|
|
202
|
+
series2: tokens.colors.neutral[300]
|
|
166
203
|
}
|
|
167
204
|
```
|
|
168
205
|
|
|
@@ -306,11 +343,11 @@ The following semantic groups are locked. Their values must not change without
|
|
|
306
343
|
explicit approval from Bradley Potts. This applies to all contributors and all
|
|
307
344
|
AI agents — apparent visual improvements still require human sign-off.
|
|
308
345
|
|
|
309
|
-
| Protected group
|
|
310
|
-
|
|
311
|
-
| `success`
|
|
312
|
-
| `warning`
|
|
313
|
-
| `danger` semantic roles
|
|
346
|
+
| Protected group | Backed by | Guarded by |
|
|
347
|
+
| ----------------------------------- | ------------------------------ | --------------------------------- |
|
|
348
|
+
| `success` | `colors.success` palette | `check:locked` + `check:contrast` |
|
|
349
|
+
| `warning` | `colors.warning` palette | `check:locked` + `check:contrast` |
|
|
350
|
+
| `danger` semantic roles | `colors.error` palette | `check:locked` + `check:contrast` |
|
|
314
351
|
| CTA / primary action / brand-action | `colors.brand` + `buttons.cta` | `check:locked` + `check:contrast` |
|
|
315
352
|
|
|
316
353
|
`check:locked` fails immediately if any protected value changes from the
|
|
@@ -354,11 +391,11 @@ If a downstream package depends on specific token paths or semantic meaning:
|
|
|
354
391
|
Every contract-affecting change is classified in `CHANGELOG.md [Unreleased]`
|
|
355
392
|
with a `Contract change type:` line before release.
|
|
356
393
|
|
|
357
|
-
| Classification
|
|
358
|
-
|
|
359
|
-
| `additive`
|
|
360
|
-
| `semantic change` | Path stays the same but meaning, intent, or visual output shifts
|
|
361
|
-
| `breaking`
|
|
394
|
+
| Classification | When to use | Examples |
|
|
395
|
+
| ----------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
|
|
396
|
+
| `additive` | New tokens, new paths, new CSS variables — existing consumers unaffected | Adding a namespace, adding a token inside an existing family |
|
|
397
|
+
| `semantic change` | Path stays the same but meaning, intent, or visual output shifts | Adjusting the role of an existing surface or text token |
|
|
398
|
+
| `breaking` | Existing consumers may need code changes | Renaming a token path, removing a namespace, changing mode names |
|
|
362
399
|
|
|
363
400
|
Renames and removals are always breaking regardless of perceived scope.
|
|
364
401
|
|
|
@@ -429,22 +466,22 @@ npm install
|
|
|
429
466
|
npm run check
|
|
430
467
|
```
|
|
431
468
|
|
|
432
|
-
This project expects Node.js `^22.12.0 || >=24.0.0` and npm `11.
|
|
469
|
+
This project expects Node.js `^22.12.0 || >=24.0.0` and npm `11.17.0`.
|
|
433
470
|
|
|
434
471
|
### Common commands
|
|
435
472
|
|
|
436
|
-
| Command
|
|
437
|
-
|
|
438
|
-
| `npm run build`
|
|
439
|
-
| `npm run check`
|
|
440
|
-
| `npm run lint`
|
|
441
|
-
| `npm run format`
|
|
442
|
-
| `npm run generate`
|
|
443
|
-
| `npm run check:manifest` | Validate public namespaces against `contract.manifest.json`
|
|
444
|
-
| `npm run check:docs`
|
|
445
|
-
| `npm run check:locked`
|
|
446
|
-
| `npm run check:contrast` | Confirm all paired tokens meet WCAG AA
|
|
447
|
-
| `npm run check:dist`
|
|
473
|
+
| Command | What it does |
|
|
474
|
+
| ------------------------ | ------------------------------------------------------------ |
|
|
475
|
+
| `npm run build` | Regenerate all outputs — run after any token source change |
|
|
476
|
+
| `npm run check` | Full validation gate — all 15 steps must pass before commit |
|
|
477
|
+
| `npm run lint` | Run ESLint against all source files |
|
|
478
|
+
| `npm run format` | Apply Prettier formatting to all files |
|
|
479
|
+
| `npm run generate` | Regenerate `src/generated/tokens.ts` from token sources only |
|
|
480
|
+
| `npm run check:manifest` | Validate public namespaces against `contract.manifest.json` |
|
|
481
|
+
| `npm run check:docs` | Validate README and TOKEN_CONTRACT headings against manifest |
|
|
482
|
+
| `npm run check:locked` | Confirm protected color families are unchanged |
|
|
483
|
+
| `npm run check:contrast` | Confirm all paired tokens meet WCAG AA |
|
|
484
|
+
| `npm run check:dist` | Confirm `dist/` artifacts are in sync with source |
|
|
448
485
|
|
|
449
486
|
### Key source areas
|
|
450
487
|
|
|
@@ -460,15 +497,15 @@ treated as downstream UI primitives.
|
|
|
460
497
|
|
|
461
498
|
## Troubleshooting
|
|
462
499
|
|
|
463
|
-
| Failure
|
|
464
|
-
|
|
465
|
-
| `check:regression` fails
|
|
466
|
-
| `check:locked` fails
|
|
467
|
-
| `check:contrast` fails
|
|
468
|
-
| `check:dist` fails
|
|
469
|
-
| `check:manifest` fails
|
|
470
|
-
| `check:docs` fails
|
|
471
|
-
| `check:classification` fails | A contract-authority file changed without a classification entry
|
|
500
|
+
| Failure | Cause | Fix |
|
|
501
|
+
| ---------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
502
|
+
| `check:regression` fails | A token value changed vs the recorded baseline | Revert the unintended change, or update the baseline if the change was intentional |
|
|
503
|
+
| `check:locked` fails | A protected color family was modified | Revert unless Bradley Potts has explicitly approved the change |
|
|
504
|
+
| `check:contrast` fails | A text/background token pair does not meet WCAG AA | Adjust the token value or the `metadata.pair` reference in the source JSON |
|
|
505
|
+
| `check:dist` fails | Generated dist is out of sync | Run `npm run build` then re-run `npm run check` |
|
|
506
|
+
| `check:manifest` fails | A namespace exists in outputs but is not declared in `contract.manifest.json` | Add the namespace to the manifest or remove it from the source |
|
|
507
|
+
| `check:docs` fails | README or TOKEN_CONTRACT.md has drifted from the manifest | Update the doc to match the current contract |
|
|
508
|
+
| `check:classification` fails | A contract-authority file changed without a classification entry | Add `Contract change type: additive`, `semantic change`, or `breaking` to `CHANGELOG.md [Unreleased]` |
|
|
472
509
|
|
|
473
510
|
## AI and automation boundaries
|
|
474
511
|
|
|
@@ -484,8 +521,8 @@ validation gates pass.
|
|
|
484
521
|
|
|
485
522
|
**Protected from automated change:** locked color families (`success`,
|
|
486
523
|
`warning`, `danger`, CTA/brand-action), `contract.manifest.json`, and
|
|
487
|
-
`src/generated/tokens.ts`. See [AGENTS.md](AGENTS.md) for full agent
|
|
488
|
-
|
|
524
|
+
`src/generated/tokens.ts`. See [AGENTS.md](AGENTS.md) for full agent governance
|
|
525
|
+
and boundary rules.
|
|
489
526
|
|
|
490
527
|
## Contributing
|
|
491
528
|
|
package/dist/index.cjs
CHANGED
|
@@ -264,7 +264,7 @@ var coreTokens = {
|
|
|
264
264
|
"card": "{colors.white}",
|
|
265
265
|
"input": "{colors.white}",
|
|
266
266
|
"overlay": "{colors.black} / 0.6",
|
|
267
|
-
"
|
|
267
|
+
"subtle": "{colors.neutral.100}",
|
|
268
268
|
"hero": "linear-gradient(135deg, {colors.indigo.500} 0%, {colors.violet.600} 100%)",
|
|
269
269
|
"hover": "{colors.neutral.100}",
|
|
270
270
|
"selected": "{colors.info.50}",
|
|
@@ -410,7 +410,7 @@ var coreTokens = {
|
|
|
410
410
|
"card": "{colors.neutral.800}",
|
|
411
411
|
"input": "{colors.neutral.700}",
|
|
412
412
|
"overlay": "{colors.black} / 0.6",
|
|
413
|
-
"
|
|
413
|
+
"subtle": "{colors.neutral.800}",
|
|
414
414
|
"hero": "linear-gradient(135deg, {colors.accent.700} 0%, {colors.accent.900} 100%)",
|
|
415
415
|
"hover": "{colors.neutral.700}",
|
|
416
416
|
"selected": "{colors.info.900}",
|
|
@@ -929,7 +929,11 @@ var coreTokens = {
|
|
|
929
929
|
"md": "1.5rem",
|
|
930
930
|
"lg": "2rem"
|
|
931
931
|
},
|
|
932
|
-
"maxWidth": "72rem"
|
|
932
|
+
"maxWidth": "72rem",
|
|
933
|
+
"maxWidthProse": "65ch"
|
|
934
|
+
},
|
|
935
|
+
"sidebar": {
|
|
936
|
+
"width": "16rem"
|
|
933
937
|
}
|
|
934
938
|
},
|
|
935
939
|
"font": {
|
|
@@ -1231,6 +1235,13 @@ var createCssVariableMap = (tokens2, options = {}) => {
|
|
|
1231
1235
|
if (container?.maxWidth) {
|
|
1232
1236
|
assign(toVariableName(prefix, "layout", "container", "max-width"), container.maxWidth);
|
|
1233
1237
|
}
|
|
1238
|
+
if (container?.maxWidthProse) {
|
|
1239
|
+
assign(toVariableName(prefix, "layout", "container", "max-width-prose"), container.maxWidthProse);
|
|
1240
|
+
}
|
|
1241
|
+
const sidebar = layout.sidebar;
|
|
1242
|
+
if (sidebar?.width) {
|
|
1243
|
+
assign(toVariableName(prefix, "layout", "sidebar", "width"), sidebar.width);
|
|
1244
|
+
}
|
|
1234
1245
|
}
|
|
1235
1246
|
const border = baseTokens.border;
|
|
1236
1247
|
if (border?.width) {
|
|
@@ -1332,13 +1343,18 @@ var generateCssVariables = (tokens2, options = {}) => {
|
|
|
1332
1343
|
const surfaceAliases = tokens2.surface ?? {};
|
|
1333
1344
|
const textAliases = tokens2.text ?? {};
|
|
1334
1345
|
const componentAliases = tokens2.component ?? {};
|
|
1346
|
+
const linkTokens = tokens2.link ?? {};
|
|
1335
1347
|
const semanticEntries = [
|
|
1336
1348
|
{ varParts: ["surface", "page"], modePath: ["surface", "page"], aliasSrc: surfaceAliases, aliasPath: ["page"] },
|
|
1337
1349
|
{ varParts: ["surface", "card"], modePath: ["surface", "card"], aliasSrc: surfaceAliases, aliasPath: ["card"] },
|
|
1338
1350
|
{ varParts: ["surface", "input"], modePath: ["surface", "input"], aliasSrc: surfaceAliases, aliasPath: ["input"] },
|
|
1339
1351
|
{ varParts: ["surface", "overlay"], modePath: ["surface", "overlay"], aliasSrc: surfaceAliases, aliasPath: ["overlay"] },
|
|
1340
|
-
{ varParts: ["surface", "
|
|
1352
|
+
{ varParts: ["surface", "subtle"], modePath: ["surface", "subtle"] },
|
|
1341
1353
|
{ varParts: ["surface", "hero"], modePath: ["surface", "hero"], aliasSrc: surfaceAliases, aliasPath: ["hero"] },
|
|
1354
|
+
{ varParts: ["surface", "hover"], modePath: ["surface", "hover"], aliasSrc: surfaceAliases, aliasPath: ["hover"] },
|
|
1355
|
+
{ varParts: ["surface", "selected"], modePath: ["surface", "selected"], aliasSrc: surfaceAliases, aliasPath: ["selected"] },
|
|
1356
|
+
{ varParts: ["surface", "active"], modePath: ["surface", "active"], aliasSrc: surfaceAliases, aliasPath: ["active"] },
|
|
1357
|
+
{ varParts: ["surface", "divider"], modePath: ["surface", "divider"], aliasSrc: surfaceAliases, aliasPath: ["divider"] },
|
|
1342
1358
|
{ varParts: ["text", "on", "page", "default"], modePath: ["text", "onPage", "default"], aliasSrc: textAliases, aliasPath: ["onPage", "default"] },
|
|
1343
1359
|
{ varParts: ["text", "on", "page", "muted"], modePath: ["text", "onPage", "muted"], aliasSrc: textAliases, aliasPath: ["onPage", "muted"] },
|
|
1344
1360
|
{ varParts: ["text", "on", "page", "subtle"], modePath: ["text", "onPage", "subtle"], aliasSrc: textAliases, aliasPath: ["onPage", "subtle"] },
|
|
@@ -1410,6 +1426,12 @@ var generateCssVariables = (tokens2, options = {}) => {
|
|
|
1410
1426
|
addBase(varName, pickSemantic(tokens2, getPath(defaultMode, modePath), ...aliasCandidate));
|
|
1411
1427
|
addDark(varName, pickSemantic(tokens2, getPath(darkMode, modePath), getPath(defaultMode, modePath), ...aliasCandidate));
|
|
1412
1428
|
});
|
|
1429
|
+
Object.entries(linkTokens).forEach(([key, value]) => {
|
|
1430
|
+
const varName = toVariableName(prefix, "link", key);
|
|
1431
|
+
const resolved = pickSemantic(tokens2, value);
|
|
1432
|
+
addBase(varName, resolved);
|
|
1433
|
+
addDark(varName, resolved);
|
|
1434
|
+
});
|
|
1413
1435
|
const rootBlock = `${selector} {
|
|
1414
1436
|
${[...baseLines, ...mapLines].join("\n")}
|
|
1415
1437
|
}`;
|
|
@@ -1461,7 +1483,11 @@ var createTailwindTheme = (source = tokens) => {
|
|
|
1461
1483
|
transitionTimingFunction: { ...source.transitions.easing },
|
|
1462
1484
|
opacity: { ...source.opacity },
|
|
1463
1485
|
maxWidth: {
|
|
1464
|
-
container: source.layout?.container?.maxWidth
|
|
1486
|
+
container: source.layout?.container?.maxWidth,
|
|
1487
|
+
prose: source.layout?.container?.maxWidthProse
|
|
1488
|
+
},
|
|
1489
|
+
width: {
|
|
1490
|
+
sidebar: source.layout?.sidebar?.width
|
|
1465
1491
|
},
|
|
1466
1492
|
borderWidth: {
|
|
1467
1493
|
DEFAULT: source.border?.width.base,
|