@phcdevworks/spectre-tokens 2.0.0 → 2.1.1
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/LICENSE +12 -13
- package/README.md +133 -1440
- package/dist/index.cjs +386 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +179 -169
- package/dist/index.d.cts +250 -14
- package/dist/index.d.ts +250 -14
- package/dist/index.js +386 -136
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/tokens/components.json +444 -105
- package/tokens/modes.json +558 -108
- package/tokens/palette.json +55 -55
- package/tokens/primitives.json +6 -0
- package/tokens/semantic-roles.json +2 -1
package/README.md
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
# @phcdevworks/spectre-tokens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/phcdevworks/spectre-tokens/issues)
|
|
4
|
+
[](https://github.com/phcdevworks/spectre-tokens/pulls)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
`@phcdevworks/spectre-tokens` is the design-token package of the Spectre system
|
|
8
|
+
for downstream Spectre packages and compatible applications.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Maintained by PHCDevworks, it defines the visual language, semantic roles, and
|
|
11
|
+
token contracts consumed downstream. It keeps visual meaning centralized in
|
|
12
|
+
token data while downstream UI packages define structure and adapter packages
|
|
13
|
+
translate those contracts for specific frameworks and runtimes.
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
[Contributing](CONTRIBUTING.md) | [Changelog](CHANGELOG.md) |
|
|
16
|
+
[Security Policy](SECURITY.md)
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
## Key capabilities
|
|
12
19
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
20
|
+
- Uses `tokens/` as the source of truth for design-token data
|
|
21
|
+
- Generates JavaScript, TypeScript, Tailwind, and CSS outputs from shared token
|
|
22
|
+
sources
|
|
23
|
+
- Defines semantic token contracts for surfaces, text, components, buttons,
|
|
24
|
+
forms, and modes
|
|
25
|
+
- Exposes primitives and semantic roles for downstream packages and compatible
|
|
26
|
+
applications
|
|
27
|
+
- Keeps visual meaning centralized so downstream consumers do not redefine token
|
|
28
|
+
contracts
|
|
18
29
|
|
|
19
30
|
## Installation
|
|
20
31
|
|
|
@@ -22,1499 +33,181 @@ JSON-first design tokens that power Spectre UI, Spectre Blocks, Spectre Astro, S
|
|
|
22
33
|
npm install @phcdevworks/spectre-tokens
|
|
23
34
|
```
|
|
24
35
|
|
|
25
|
-
## Quick
|
|
36
|
+
## Quick start
|
|
26
37
|
|
|
27
|
-
###
|
|
38
|
+
### CSS import
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
If you’re not using a bundler, copy `dist/index.css` into your app and link it.
|
|
40
|
+
Import the generated CSS variables:
|
|
32
41
|
|
|
33
42
|
```css
|
|
34
|
-
@import
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
or:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
import "@phcdevworks/spectre-tokens/dist/index.css";
|
|
43
|
+
@import '@phcdevworks/spectre-tokens/index.css';
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```css
|
|
46
|
-
.my-button {
|
|
47
|
-
background: var(--sp-button-primary-bg);
|
|
48
|
-
color: var(--sp-button-primary-text);
|
|
49
|
-
padding: var(--sp-space-12) var(--sp-space-24);
|
|
50
|
-
border-radius: var(--sp-radius-md);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
> Note: Raw palette tokens like `--sp-color-brand-500` are stable utilities, but they do not adapt across modes by themselves. Prefer semantic tokens (`surface.*`, `text.*`, `component.*`, `buttons.*`, `forms.*`) for theme-aware UI.
|
|
55
|
-
|
|
56
|
-
### Option 2: JavaScript/TypeScript
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
import tokens from "@phcdevworks/spectre-tokens";
|
|
60
|
-
|
|
61
|
-
const styles = {
|
|
62
|
-
color: tokens.colors.brand["500"],
|
|
63
|
-
padding: `${tokens.space["12"]} ${tokens.space["24"]}`,
|
|
64
|
-
borderRadius: tokens.radii.md,
|
|
65
|
-
};
|
|
66
|
-
```
|
|
46
|
+
### Token usage
|
|
67
47
|
|
|
68
|
-
|
|
48
|
+
Load the token object in JavaScript or TypeScript:
|
|
69
49
|
|
|
70
50
|
```ts
|
|
71
|
-
|
|
72
|
-
import { tailwindPreset } from "@phcdevworks/spectre-tokens";
|
|
73
|
-
|
|
74
|
-
export default {
|
|
75
|
-
presets: [tailwindPreset],
|
|
76
|
-
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
77
|
-
};
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```html
|
|
81
|
-
<!-- Use Spectre tokens via Tailwind utilities -->
|
|
82
|
-
<button class="bg-brand-500 text-white px-6 py-3 rounded-md shadow-md">
|
|
83
|
-
Click me
|
|
84
|
-
</button>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
> Tip: Tailwind palette utilities like `bg-brand-500` are stable, but they won’t automatically adapt across modes. For theme-aware UI, prefer semantic CSS variables (`surface.*`, `text.*`, `component.*`) or Spectre UI recipes.
|
|
88
|
-
|
|
89
|
-
## Usage
|
|
90
|
-
|
|
91
|
-
### 1. Import tokens (JS/TS)
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
import tokens, {
|
|
95
|
-
tailwindTheme,
|
|
96
|
-
tailwindPreset,
|
|
97
|
-
generateCssVariables,
|
|
98
|
-
} from "@phcdevworks/spectre-tokens";
|
|
51
|
+
import tokens from '@phcdevworks/spectre-tokens'
|
|
99
52
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
**Exports:**
|
|
106
|
-
|
|
107
|
-
- `tokens` (default export): Complete token object with flattened structure for easy access
|
|
108
|
-
- `tailwindTheme`: Ready-to-use Tailwind theme object
|
|
109
|
-
- `tailwindPreset`: Preset for Tailwind config (includes theme)
|
|
110
|
-
- `generateCssVariables()`: Generate custom `--sp-*` CSS variable strings with scoped selectors or prefixes
|
|
111
|
-
|
|
112
|
-
`generateCssVariables()` returns a CSS **string** (e.g. `:root { --sp-... }`) you can write to a file or inject into a page.
|
|
113
|
-
|
|
114
|
-
**Token Structure (high-level namespaces):**
|
|
115
|
-
|
|
116
|
-
The `tokens` object includes:
|
|
117
|
-
|
|
118
|
-
- `colors`: Color palettes (brand, neutral, accent, success, warning, error, info, focus)
|
|
119
|
-
- `space`: Spacing scale (0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96)
|
|
120
|
-
- `layout`: Semantic layout tokens (section, stack, container padding/gaps)
|
|
121
|
-
- `radii`: Border radius values (none, sm, md, lg, pill)
|
|
122
|
-
- `typography`: Font families and complete typography scale with metadata
|
|
123
|
-
- `font`: Simplified font size/lineHeight/weight tokens for quick access
|
|
124
|
-
- `shadows`: Box shadow tokens (none, sm, md, lg)
|
|
125
|
-
- `breakpoints`: Responsive breakpoints (sm, md, lg, xl, 2xl)
|
|
126
|
-
- `zIndex`: Z-index scale (base, dropdown, sticky, fixed, overlay, modal, popover, tooltip)
|
|
127
|
-
- `transitions`: Duration and easing tokens
|
|
128
|
-
- `animations`: Animation presets with duration, easing, and keyframe references
|
|
129
|
-
- `buttons`: Button state tokens (primary, secondary, ghost, danger, success)
|
|
130
|
-
- `forms`: Form state tokens (default, hover, focus, valid, invalid, disabled)
|
|
131
|
-
- `accessibility`: WCAG compliance tokens (focus ring, touch targets, min text size)
|
|
132
|
-
- `opacity`: Opacity scale (hover, active, disabled, focus, overlay, tooltip)
|
|
133
|
-
- `borders`: Border color tokens
|
|
134
|
-
- `surface`: Semantic surface backgrounds (page, card, input, overlay)
|
|
135
|
-
- `text`: Semantic text colors (onPage, onSurface with default/muted/subtle/meta)
|
|
136
|
-
- `component`: Component-specific tokens (card, input, button, badge, iconBox)
|
|
137
|
-
- `modes`: Theme mode definitions (default/light and dark)
|
|
138
|
-
|
|
139
|
-
> Tokens may exist before UI primitives land in `@phcdevworks/spectre-ui`. Tokens define meaning; UI defines structure.
|
|
140
|
-
|
|
141
|
-
### 2. CSS variables
|
|
142
|
-
|
|
143
|
-
```css
|
|
144
|
-
@import "@phcdevworks/spectre-tokens/dist/index.css";
|
|
145
|
-
|
|
146
|
-
.button {
|
|
147
|
-
color: var(--sp-text-on-page-default);
|
|
148
|
-
padding-inline: var(--sp-space-16);
|
|
149
|
-
border-radius: var(--sp-radius-pill);
|
|
150
|
-
box-shadow: var(--sp-shadow-md);
|
|
53
|
+
const card = {
|
|
54
|
+
background: tokens.surface.page,
|
|
55
|
+
color: tokens.text.onPage.default,
|
|
56
|
+
padding: tokens.space['16'],
|
|
57
|
+
borderRadius: tokens.radii.md
|
|
151
58
|
}
|
|
152
59
|
```
|
|
153
60
|
|
|
154
|
-
|
|
61
|
+
### Tailwind preset usage
|
|
155
62
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
selector: ".spectre-scope",
|
|
159
|
-
prefix: "sp",
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 3. Tailwind integration
|
|
63
|
+
Use the generated Tailwind preset when you want the package to populate theme
|
|
64
|
+
values from the token contract:
|
|
164
65
|
|
|
165
66
|
```ts
|
|
166
67
|
// tailwind.config.ts
|
|
167
|
-
import { tailwindPreset } from
|
|
68
|
+
import { tailwindPreset } from '@phcdevworks/spectre-tokens'
|
|
168
69
|
|
|
169
70
|
export default {
|
|
170
|
-
presets: [tailwindPreset]
|
|
171
|
-
};
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Works with Tailwind 3.x and 4.x through the classic config API; no Oxide lock-in required.
|
|
175
|
-
|
|
176
|
-
## Token Surfaces
|
|
177
|
-
|
|
178
|
-
### Conversion-ready button variants
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
tokens.buttons.primary; // CTA baseline
|
|
182
|
-
tokens.buttons.secondary; // Outlined
|
|
183
|
-
tokens.buttons.ghost; // Low-emphasis
|
|
184
|
-
tokens.buttons.danger; // Destructive
|
|
185
|
-
tokens.buttons.success; // Confirmations
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Each variant ships with `bg`, `bgHover`, `bgActive`, `bgDisabled`, `text`, `textDisabled`, `border`, and `borderDisabled`, ensuring consistent CRO states.
|
|
189
|
-
|
|
190
|
-
```css
|
|
191
|
-
.cta-button {
|
|
192
|
-
background: var(--sp-button-primary-bg);
|
|
193
|
-
color: var(--sp-button-primary-text);
|
|
194
|
-
}
|
|
195
|
-
.cta-button:hover {
|
|
196
|
-
background: var(--sp-button-primary-bg-hover);
|
|
71
|
+
presets: [tailwindPreset]
|
|
197
72
|
}
|
|
198
73
|
```
|
|
199
74
|
|
|
200
|
-
|
|
75
|
+
Prefer semantic tokens such as `surface`, `text`, `component`, `buttons`, and
|
|
76
|
+
`forms` for application UI. Raw palette values remain available when fixed color
|
|
77
|
+
access is appropriate.
|
|
201
78
|
|
|
202
|
-
|
|
203
|
-
tokens.forms.default;
|
|
204
|
-
tokens.forms.hover;
|
|
205
|
-
tokens.forms.focus;
|
|
206
|
-
tokens.forms.valid;
|
|
207
|
-
tokens.forms.invalid;
|
|
208
|
-
tokens.forms.disabled;
|
|
209
|
-
```
|
|
79
|
+
## What this package owns
|
|
210
80
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
.input.error {
|
|
218
|
-
border-color: var(--sp-form-invalid-border);
|
|
219
|
-
background: var(--sp-form-invalid-bg);
|
|
220
|
-
}
|
|
221
|
-
```
|
|
81
|
+
- Visual language expressed as token data in `tokens/`
|
|
82
|
+
- Semantic roles and token contracts consumed downstream
|
|
83
|
+
- Generated token outputs for JavaScript, TypeScript, Tailwind, and CSS
|
|
84
|
+
variables
|
|
85
|
+
- Theme and mode definitions used by downstream consumers
|
|
222
86
|
|
|
223
|
-
###
|
|
87
|
+
### Token model
|
|
224
88
|
|
|
225
|
-
|
|
226
|
-
tokens.accessibility.focusRing.width; // 2px
|
|
227
|
-
tokens.accessibility.focusRing.offset; // 2px
|
|
228
|
-
tokens.accessibility.minTouchTarget; // 44px (WCAG 2.5.5)
|
|
229
|
-
tokens.accessibility.minTextSize; // 16px
|
|
230
|
-
tokens.colors.focus.primary; // Brand-aligned focus
|
|
231
|
-
tokens.colors.focus.error; // Error state focus
|
|
232
|
-
tokens.colors.focus.info; // Info state focus
|
|
233
|
-
```
|
|
89
|
+
The generated token object includes these namespaces:
|
|
234
90
|
|
|
235
|
-
|
|
91
|
+
- `colors`
|
|
92
|
+
- `space`
|
|
93
|
+
- `layout`
|
|
94
|
+
- `radii`
|
|
95
|
+
- `typography`
|
|
96
|
+
- `font`
|
|
97
|
+
- `shadows`
|
|
98
|
+
- `breakpoints`
|
|
99
|
+
- `zIndex`
|
|
100
|
+
- `transitions`
|
|
101
|
+
- `animations`
|
|
102
|
+
- `opacity`
|
|
103
|
+
- `border`
|
|
104
|
+
- `accessibility`
|
|
105
|
+
- `buttons`
|
|
106
|
+
- `forms`
|
|
107
|
+
- `surface`
|
|
108
|
+
- `text`
|
|
109
|
+
- `component`
|
|
110
|
+
- `modes`
|
|
236
111
|
|
|
237
|
-
|
|
238
|
-
tokens.animations.fadeIn;
|
|
239
|
-
tokens.animations.slideDown;
|
|
240
|
-
tokens.animations.scaleIn;
|
|
241
|
-
tokens.animations.bounce;
|
|
242
|
-
tokens.animations.shake;
|
|
243
|
-
tokens.animations.pulse;
|
|
244
|
-
```
|
|
112
|
+
### Themes and modes
|
|
245
113
|
|
|
246
|
-
|
|
247
|
-
.
|
|
248
|
-
animation: fade-in var(--sp-animation-fade-in-duration)
|
|
249
|
-
var(--sp-animation-fade-in-easing);
|
|
250
|
-
}
|
|
251
|
-
```
|
|
114
|
+
The package includes mode-aware semantic tokens under `modes`, with `default`
|
|
115
|
+
and `dark` mode definitions in the generated output.
|
|
252
116
|
|
|
253
|
-
|
|
117
|
+
Raw palette tokens are stable values. Semantic tokens are the preferred
|
|
118
|
+
interface for theme-aware usage because they can map across modes without
|
|
119
|
+
changing consumer code.
|
|
254
120
|
|
|
255
|
-
|
|
256
|
-
tokens.opacity.hover; // "0.92"
|
|
257
|
-
tokens.opacity.active; // "0.84"
|
|
258
|
-
tokens.opacity.disabled; // "0.38"
|
|
259
|
-
tokens.opacity.overlay; // "0.5"
|
|
260
|
-
tokens.opacity.focus; // "1"
|
|
261
|
-
tokens.opacity.tooltip; // "0.95"
|
|
262
|
-
```
|
|
121
|
+
## What this package does not own
|
|
263
122
|
|
|
264
|
-
|
|
123
|
+
- Component structure or composition That belongs in downstream UI packages such
|
|
124
|
+
as [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
|
|
125
|
+
- Framework-specific delivery Adapter packages translate Spectre contracts for
|
|
126
|
+
specific frameworks and runtimes.
|
|
127
|
+
- Local redefinition of token meaning Downstream consumers should consume these
|
|
128
|
+
contracts rather than recreate them independently.
|
|
265
129
|
|
|
266
|
-
|
|
130
|
+
## Package exports / API surface
|
|
267
131
|
|
|
268
|
-
|
|
132
|
+
### Root package
|
|
269
133
|
|
|
270
|
-
|
|
134
|
+
`@phcdevworks/spectre-tokens` exports:
|
|
271
135
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
tokens.font["2xl"]; // { size: "1.875rem", lineHeight: "2.5rem", weight: 600 }
|
|
279
|
-
```
|
|
136
|
+
- `default` / `tokens`
|
|
137
|
+
- `tailwindTheme`
|
|
138
|
+
- `tailwindPreset`
|
|
139
|
+
- `generateCssVariables()`
|
|
140
|
+
- TypeScript types including `SpectreTokens`, `TailwindTheme`,
|
|
141
|
+
`SpectreModeTokens`, and `SpectreModeName`
|
|
280
142
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
For advanced typography with font families and additional properties like letter spacing:
|
|
143
|
+
Example:
|
|
284
144
|
|
|
285
145
|
```ts
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// Typography scale (includes all font properties plus letter spacing)
|
|
292
|
-
tokens.typography.scale.xs; // { fontSize: "0.75rem", lineHeight: "1.25rem", fontWeight: 400, letterSpacing: "0.02em" }
|
|
293
|
-
tokens.typography.scale.sm; // { fontSize: "0.875rem", lineHeight: "1.5rem", fontWeight: 400 }
|
|
294
|
-
tokens.typography.scale.md; // { fontSize: "1rem", lineHeight: "1.75rem", fontWeight: 500 }
|
|
295
|
-
tokens.typography.scale.lg; // { fontSize: "1.25rem", lineHeight: "2rem", fontWeight: 600 }
|
|
296
|
-
tokens.typography.scale.xl; // { fontSize: "1.5rem", lineHeight: "2.125rem", fontWeight: 600 }
|
|
297
|
-
tokens.typography.scale["2xl"]; // { fontSize: "1.875rem", lineHeight: "2.5rem", fontWeight: 700 }
|
|
298
|
-
tokens.typography.scale["3xl"]; // { fontSize: "2.25rem", lineHeight: "2.75rem", fontWeight: 700 }
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
**When to use which:**
|
|
302
|
-
|
|
303
|
-
- Use `tokens.font.*` for quick, simple typography applications
|
|
304
|
-
- Use `tokens.typography.*` when you need font families or letter spacing
|
|
305
|
-
- Use `tokens.typography.scale.*` for complete typographic control
|
|
306
|
-
|
|
307
|
-
### Typography CSS Variables
|
|
308
|
-
|
|
309
|
-
All typography tokens are available as CSS variables:
|
|
310
|
-
|
|
311
|
-
```css
|
|
312
|
-
.heading {
|
|
313
|
-
font-family: var(--sp-typography-family-sans);
|
|
314
|
-
font-size: var(--sp-font-xl-size);
|
|
315
|
-
line-height: var(--sp-font-xl-line-height);
|
|
316
|
-
font-weight: var(--sp-font-xl-weight);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
.body-text {
|
|
320
|
-
font-size: var(--sp-font-md-size);
|
|
321
|
-
line-height: var(--sp-font-md-line-height);
|
|
322
|
-
font-weight: var(--sp-font-md-weight);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.code-block {
|
|
326
|
-
font-family: var(--sp-typography-family-mono);
|
|
327
|
-
font-size: var(--sp-font-sm-size);
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Badge tokens
|
|
332
|
-
|
|
333
|
-
Badges have background and text colors for five semantic variants:
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
// Access via component namespace
|
|
337
|
-
tokens.component.badge.neutralBg; // "#f1f5f9" (light) / "#334155" (dark)
|
|
338
|
-
tokens.component.badge.neutralText; // "#334155" (light) / "#f1f5f9" (dark)
|
|
339
|
-
tokens.component.badge.infoBg; // "#dbeafe" (light) / "#1e40af" (dark)
|
|
340
|
-
tokens.component.badge.infoText; // "#1d4ed8" (light) / "#dbeafe" (dark)
|
|
341
|
-
tokens.component.badge.successBg; // "#dcfce7" (light) / "#166534" (dark)
|
|
342
|
-
tokens.component.badge.successText; // "#15803d" (light) / "#dcfce7" (dark)
|
|
343
|
-
tokens.component.badge.warningBg; // "#fef3c7" (light) / "#92400e" (dark)
|
|
344
|
-
tokens.component.badge.warningText; // "#b45309" (light) / "#fef3c7" (dark)
|
|
345
|
-
tokens.component.badge.dangerBg; // "#fee2e2" (light) / "#991b1b" (dark)
|
|
346
|
-
tokens.component.badge.dangerText; // "#b91c1c" (light) / "#fee2e2" (dark)
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**CSS Variables:**
|
|
350
|
-
|
|
351
|
-
```css
|
|
352
|
-
.badge {
|
|
353
|
-
background: var(--sp-badge-neutral-bg);
|
|
354
|
-
color: var(--sp-badge-neutral-text);
|
|
355
|
-
padding: 0.25rem 0.5rem;
|
|
356
|
-
border-radius: var(--sp-radius-sm);
|
|
357
|
-
font-size: var(--sp-font-xs-size);
|
|
358
|
-
font-weight: var(--sp-font-xs-weight);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.badge--info {
|
|
362
|
-
background: var(--sp-badge-info-bg);
|
|
363
|
-
color: var(--sp-badge-info-text);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
.badge--success {
|
|
367
|
-
background: var(--sp-badge-success-bg);
|
|
368
|
-
color: var(--sp-badge-success-text);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.badge--warning {
|
|
372
|
-
background: var(--sp-badge-warning-bg);
|
|
373
|
-
color: var(--sp-badge-warning-text);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
.badge--danger {
|
|
377
|
-
background: var(--sp-badge-danger-bg);
|
|
378
|
-
color: var(--sp-badge-danger-text);
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### Icon Box tokens
|
|
383
|
-
|
|
384
|
-
Icon boxes are decorative containers for icons with semantic color states:
|
|
385
|
-
|
|
386
|
-
```ts
|
|
387
|
-
// Background and border
|
|
388
|
-
tokens.component.iconBox.bg; // "#ffffff" (light) / "#1e293b" (dark)
|
|
389
|
-
tokens.component.iconBox.border; // "#e2e8f0" (light) / "#334155" (dark)
|
|
390
|
-
|
|
391
|
-
// Icon colors for different states
|
|
392
|
-
tokens.component.iconBox.iconDefault; // "#6c32e6" (light) / "#a37aff" (dark)
|
|
393
|
-
tokens.component.iconBox.iconSuccess; // "#16a34a" (light) / "#4ade80" (dark)
|
|
394
|
-
tokens.component.iconBox.iconWarning; // "#d97706" (light) / "#fbbf24" (dark)
|
|
395
|
-
tokens.component.iconBox.iconDanger; // "#dc2626" (light) / "#ef4444" (dark)
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**CSS Variables:**
|
|
399
|
-
|
|
400
|
-
```css
|
|
401
|
-
.icon-box {
|
|
402
|
-
background: var(--sp-icon-box-bg);
|
|
403
|
-
border: 1px solid var(--sp-icon-box-border);
|
|
404
|
-
border-radius: var(--sp-radius-md);
|
|
405
|
-
padding: var(--sp-space-12);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
.icon-box__icon {
|
|
409
|
-
color: var(--sp-icon-box-icon-default);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
.icon-box--success .icon-box__icon {
|
|
413
|
-
color: var(--sp-icon-box-icon-success);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
.icon-box--warning .icon-box__icon {
|
|
417
|
-
color: var(--sp-icon-box-icon-warning);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.icon-box--danger .icon-box__icon {
|
|
421
|
-
color: var(--sp-icon-box-icon-danger);
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Meta text
|
|
426
|
-
|
|
427
|
-
For secondary/metadata text styling:
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
tokens.text.onPage.meta; // Metadata text on page backgrounds
|
|
431
|
-
tokens.text.onSurface.meta; // Metadata text on card/surface backgrounds
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
```css
|
|
435
|
-
.timestamp,
|
|
436
|
-
.byline {
|
|
437
|
-
color: var(--sp-text-on-page-meta);
|
|
438
|
-
font-size: var(--sp-font-sm-size);
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### WCAG targets
|
|
443
|
-
|
|
444
|
-
Spectre encodes accessibility constraints (focus rings, touch targets, and contrast-minded semantic roles) at the token level. Always validate final UI implementations with tools like the WebAIM Contrast Checker.
|
|
445
|
-
|
|
446
|
-
Always re-run final UI implementations through tools like [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/).
|
|
447
|
-
|
|
448
|
-
## Spacing & Layout Tokens
|
|
449
|
-
|
|
450
|
-
- **8px grid**: `space.*` follows an 8px rhythm with a single 4px micro step for fine-grain alignment. Scale: 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 (all in px, emitted as rem).
|
|
451
|
-
- **Semantic layout**: `layout.section.padding.{sm,md,lg}` → `space.24/32/48`; `layout.section.gap.{sm,md,lg}` → `space.16/24/32`; `layout.stack.gap.{sm,md,lg}` → `space.8/12/16`; `layout.container.paddingInline.{sm,md,lg}` → `space.16/24/32`.
|
|
452
|
-
- **Outputs**: CSS vars like `--sp-space-24` and `--sp-layout-section-padding-md`; Tailwind `theme.spacing` is sourced from `space.*`.
|
|
453
|
-
- **Usage**: Use `layout.*` for consistent gutters/padding rather than ad-hoc numbers. Example: `gap: var(--sp-layout-stack-gap-md);` or `padding-inline: var(--sp-layout-container-padding-inline-lg);`.
|
|
454
|
-
- **Responsiveness lives in Spectre UI**: apply breakpoint logic and component behavior in `@phcdevworks/spectre-ui` or consumers; keep tokens meaning-only.
|
|
455
|
-
|
|
456
|
-
## Surface & Typography Roles
|
|
457
|
-
|
|
458
|
-
- `surface.page`, `surface.card`, `surface.input`, `surface.overlay`: semantic backgrounds for the app canvas, containers/tiles, form fields, and modal/dropdown layers.
|
|
459
|
-
- `text.onPage.*` vs `text.onSurface.*`: use `onPage` for copy sitting directly on the page canvas; use `onSurface` for text inside cards, tiles, inputs, overlays, and other elevated surfaces.
|
|
460
|
-
- `text.onPage.default`, `text.onPage.muted`, `text.onPage.subtle`, `text.onPage.meta`
|
|
461
|
-
- `text.onSurface.default`, `text.onSurface.muted`, `text.onSurface.subtle`, `text.onSurface.meta`
|
|
462
|
-
- `component.card.text`/`textMuted`, `component.input.text`/`placeholder`, `component.button.textDefault`/`textOnPrimary`, and `component.badge.*` alias the underlying semantic roles to keep component defaults aligned.
|
|
463
|
-
|
|
464
|
-
## Modes & Theme Switching
|
|
465
|
-
|
|
466
|
-
Spectre tokens ship with a comprehensive `modes` system for theme support:
|
|
467
|
-
|
|
468
|
-
```ts
|
|
469
|
-
tokens.modes.default; // Light theme semantic tokens
|
|
470
|
-
tokens.modes.dark; // Dark theme semantic tokens
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Mode Structure
|
|
474
|
-
|
|
475
|
-
Each mode contains semantic tokens that adapt to the theme:
|
|
476
|
-
|
|
477
|
-
```ts
|
|
478
|
-
// Light mode (tokens.modes.default)
|
|
479
|
-
{
|
|
480
|
-
surface: {
|
|
481
|
-
page: { value: "#f8fafc" },
|
|
482
|
-
card: { value: "#ffffff" },
|
|
483
|
-
input: { value: "#ffffff" },
|
|
484
|
-
overlay: { value: "rgba(15,23,42,0.6)" }
|
|
485
|
-
},
|
|
486
|
-
text: {
|
|
487
|
-
onPage: {
|
|
488
|
-
default: { value: "#0f172a" },
|
|
489
|
-
muted: { value: "#475569" },
|
|
490
|
-
subtle: { value: "#94a3b8" },
|
|
491
|
-
meta: { value: "#94a3b8" }
|
|
492
|
-
},
|
|
493
|
-
onSurface: { /* ... */ }
|
|
494
|
-
},
|
|
495
|
-
component: {
|
|
496
|
-
card: { text: { value: "#0f172a" }, textMuted: { value: "#6b7280" } },
|
|
497
|
-
input: { text: { value: "#0f172a" }, placeholder: { value: "#94a3b8" } },
|
|
498
|
-
button: { textDefault: { value: "#0f172a" }, textOnPrimary: { value: "#ffffff" } },
|
|
499
|
-
badge: { /* ... */ },
|
|
500
|
-
iconBox: { /* ... */ }
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Dark mode (tokens.modes.dark) - same structure with dark-adapted values
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### Accessing Semantic Tokens
|
|
508
|
-
|
|
509
|
-
The TypeScript library flattens mode tokens to the root level for convenient access:
|
|
510
|
-
|
|
511
|
-
> Note: The JS `tokens.*` values resolve to the default mode. Theme switching happens in CSS via `:root[data-spectre-theme="dark"]` and semantic CSS variables.
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
// These are automatically resolved from modes.default
|
|
515
|
-
tokens.surface.page; // "#f8fafc" in light mode
|
|
516
|
-
tokens.text.onPage.default; // "#0f172a" in light mode
|
|
517
|
-
tokens.component.card.text; // "#0f172a" in light mode
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
### CSS Variable Output
|
|
521
|
-
|
|
522
|
-
The CSS generator outputs mode-specific variables:
|
|
523
|
-
|
|
524
|
-
```css
|
|
525
|
-
/* Default (light) theme */
|
|
526
|
-
:root {
|
|
527
|
-
--sp-surface-page: #f8fafc;
|
|
528
|
-
--sp-surface-card: #ffffff;
|
|
529
|
-
--sp-text-on-page-default: #0f172a;
|
|
530
|
-
/* ... */
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/* Dark theme */
|
|
534
|
-
:root[data-spectre-theme="dark"] {
|
|
535
|
-
--sp-surface-page: #0f172a;
|
|
536
|
-
--sp-surface-card: #1e293b;
|
|
537
|
-
--sp-text-on-page-default: #f8fafc;
|
|
538
|
-
/* ... */
|
|
539
|
-
}
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
### Enabling Dark Mode
|
|
543
|
-
|
|
544
|
-
Toggle themes by setting the `data-spectre-theme` attribute:
|
|
545
|
-
|
|
546
|
-
```html
|
|
547
|
-
<!-- Light mode (default) -->
|
|
548
|
-
<html>
|
|
549
|
-
<!-- Your content -->
|
|
550
|
-
</html>
|
|
551
|
-
|
|
552
|
-
<!-- Dark mode -->
|
|
553
|
-
<html data-spectre-theme="dark">
|
|
554
|
-
<!-- Your content -->
|
|
555
|
-
</html>
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
```js
|
|
559
|
-
function setTheme(theme) {
|
|
560
|
-
const html = document.documentElement;
|
|
561
|
-
if (!theme || theme === "default") html.removeAttribute("data-spectre-theme");
|
|
562
|
-
else html.setAttribute("data-spectre-theme", theme);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function toggleTheme() {
|
|
566
|
-
const html = document.documentElement;
|
|
567
|
-
const currentTheme = html.getAttribute("data-spectre-theme");
|
|
568
|
-
setTheme(currentTheme === "dark" ? "default" : "dark");
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Set based on user preference
|
|
572
|
-
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
573
|
-
setTheme("dark");
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
### Semantic Token Benefits
|
|
578
|
-
|
|
579
|
-
- **Automatic theme adaptation**: All semantic tokens (surface, text, component) automatically adapt when switching themes
|
|
580
|
-
- **No manual color management**: Reference semantic tokens instead of raw colors
|
|
581
|
-
- **Consistent UX**: Components using semantic tokens maintain proper contrast in both themes
|
|
582
|
-
- **Type-safe**: TypeScript definitions ensure you only use valid semantic token paths
|
|
583
|
-
|
|
584
|
-
### Key CSS variables
|
|
585
|
-
|
|
586
|
-
These variables are the contract consumed by `@phcdevworks/spectre-ui`; removing or renaming them will break downstream UI packages.
|
|
587
|
-
|
|
588
|
-
- **Surface**: `--sp-surface-page`, `--sp-surface-card`, `--sp-surface-input`, `--sp-surface-overlay`
|
|
589
|
-
- **Text**: `--sp-text-on-page-default`, `--sp-text-on-page-muted`, `--sp-text-on-page-subtle`, `--sp-text-on-page-meta`, `--sp-text-on-surface-default`, `--sp-text-on-surface-muted`, `--sp-text-on-surface-subtle`, `--sp-text-on-surface-meta`
|
|
590
|
-
- **Components**: `--sp-component-card-text`, `--sp-component-card-text-muted`, `--sp-component-input-text`, `--sp-component-input-placeholder`, `--sp-badge-neutral-bg`, `--sp-badge-neutral-text`
|
|
591
|
-
- **Buttons**: `--sp-button-primary-bg`, `--sp-button-primary-text`, `--sp-button-secondary-bg`, `--sp-button-secondary-text`, `--sp-button-ghost-bg`, `--sp-button-ghost-text`
|
|
592
|
-
- **Typography**: `--sp-font-{xs,sm,md,lg,xl}-size`, `--sp-font-{xs,sm,md,lg,xl}-line-height`, `--sp-font-{xs,sm,md,lg,xl}-weight`
|
|
593
|
-
|
|
594
|
-
## Complete Token Reference
|
|
595
|
-
|
|
596
|
-
### Color Tokens
|
|
597
|
-
|
|
598
|
-
```ts
|
|
599
|
-
// Brand colors (purple)
|
|
600
|
-
tokens.colors.brand["50"]; // "#f5f0ff"
|
|
601
|
-
tokens.colors.brand["500"]; // "#8652ff"
|
|
602
|
-
tokens.colors.brand["900"]; // "#241147"
|
|
603
|
-
|
|
604
|
-
// Neutral grays
|
|
605
|
-
tokens.colors.neutral["50"]; // "#f8fafc"
|
|
606
|
-
tokens.colors.neutral["500"]; // "#64748b"
|
|
607
|
-
tokens.colors.neutral["900"]; // "#0f172a"
|
|
608
|
-
|
|
609
|
-
// Accent (teal/cyan)
|
|
610
|
-
tokens.colors.accent["500"]; // "#03e6b3"
|
|
611
|
-
|
|
612
|
-
// Semantic colors
|
|
613
|
-
tokens.colors.success["500"]; // "#22c55e"
|
|
614
|
-
tokens.colors.warning["500"]; // "#f59e0b"
|
|
615
|
-
tokens.colors.error["500"]; // "#ef4444"
|
|
616
|
-
tokens.colors.info["500"]; // "#3b82f6"
|
|
617
|
-
|
|
618
|
-
// Focus colors
|
|
619
|
-
tokens.colors.focus.primary; // "#8652ff"
|
|
620
|
-
tokens.colors.focus.error; // "#ef4444"
|
|
621
|
-
tokens.colors.focus.info; // "#3b82f6"
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### Spacing Tokens
|
|
625
|
-
|
|
626
|
-
```ts
|
|
627
|
-
tokens.space["0"]; // "0rem"
|
|
628
|
-
tokens.space["4"]; // "0.25rem" (4px)
|
|
629
|
-
tokens.space["8"]; // "0.5rem" (8px)
|
|
630
|
-
tokens.space["16"]; // "1rem" (16px)
|
|
631
|
-
tokens.space["24"]; // "1.5rem" (24px)
|
|
632
|
-
tokens.space["32"]; // "2rem" (32px)
|
|
633
|
-
tokens.space["48"]; // "3rem" (48px)
|
|
634
|
-
tokens.space["64"]; // "4rem" (64px)
|
|
635
|
-
tokens.space["96"]; // "6rem" (96px)
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Layout Tokens
|
|
639
|
-
|
|
640
|
-
```ts
|
|
641
|
-
// Section padding
|
|
642
|
-
tokens.layout.section.padding.sm; // "1.5rem"
|
|
643
|
-
tokens.layout.section.padding.md; // "2rem"
|
|
644
|
-
tokens.layout.section.padding.lg; // "3rem"
|
|
645
|
-
|
|
646
|
-
// Section gaps
|
|
647
|
-
tokens.layout.section.gap.sm; // "1rem"
|
|
648
|
-
tokens.layout.section.gap.md; // "1.5rem"
|
|
649
|
-
tokens.layout.section.gap.lg; // "2rem"
|
|
650
|
-
|
|
651
|
-
// Stack gaps (tighter spacing for related elements)
|
|
652
|
-
tokens.layout.stack.gap.sm; // "0.5rem"
|
|
653
|
-
tokens.layout.stack.gap.md; // "0.75rem"
|
|
654
|
-
tokens.layout.stack.gap.lg; // "1rem"
|
|
655
|
-
|
|
656
|
-
// Container inline padding
|
|
657
|
-
tokens.layout.container.paddingInline.sm; // "1rem"
|
|
658
|
-
tokens.layout.container.paddingInline.md; // "1.5rem"
|
|
659
|
-
tokens.layout.container.paddingInline.lg; // "2rem"
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
### Border Radius Tokens
|
|
663
|
-
|
|
664
|
-
```ts
|
|
665
|
-
tokens.radii.none; // "0"
|
|
666
|
-
tokens.radii.sm; // "2px"
|
|
667
|
-
tokens.radii.md; // "4px"
|
|
668
|
-
tokens.radii.lg; // "8px"
|
|
669
|
-
tokens.radii.pill; // "999px"
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### Shadow Tokens
|
|
673
|
-
|
|
674
|
-
```ts
|
|
675
|
-
tokens.shadows.none; // "none"
|
|
676
|
-
tokens.shadows.sm; // "0 1px 2px 0 rgba(15, 23, 42, 0.08)"
|
|
677
|
-
tokens.shadows.md; // "0 3px 8px -1px rgba(15, 23, 42, 0.1)"
|
|
678
|
-
tokens.shadows.lg; // "0 8px 20px -4px rgba(15, 23, 42, 0.18)"
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Breakpoint Tokens
|
|
682
|
-
|
|
683
|
-
```ts
|
|
684
|
-
tokens.breakpoints.sm; // "640px"
|
|
685
|
-
tokens.breakpoints.md; // "768px"
|
|
686
|
-
tokens.breakpoints.lg; // "1024px"
|
|
687
|
-
tokens.breakpoints.xl; // "1280px"
|
|
688
|
-
tokens.breakpoints["2xl"]; // "1536px"
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### Z-Index Tokens
|
|
146
|
+
import tokens, {
|
|
147
|
+
generateCssVariables,
|
|
148
|
+
tailwindPreset,
|
|
149
|
+
tailwindTheme
|
|
150
|
+
} from '@phcdevworks/spectre-tokens'
|
|
692
151
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
tokens.zIndex.fixed; // "1200"
|
|
698
|
-
tokens.zIndex.overlay; // "1300"
|
|
699
|
-
tokens.zIndex.modal; // "1400"
|
|
700
|
-
tokens.zIndex.popover; // "1500"
|
|
701
|
-
tokens.zIndex.tooltip; // "1600"
|
|
152
|
+
const css = generateCssVariables(tokens, {
|
|
153
|
+
selector: ':root',
|
|
154
|
+
prefix: 'sp'
|
|
155
|
+
})
|
|
702
156
|
```
|
|
703
157
|
|
|
704
|
-
###
|
|
158
|
+
### CSS entry point
|
|
705
159
|
|
|
706
|
-
|
|
707
|
-
// Duration
|
|
708
|
-
tokens.transitions.duration.instant; // "75ms"
|
|
709
|
-
tokens.transitions.duration.fast; // "150ms"
|
|
710
|
-
tokens.transitions.duration.base; // "200ms"
|
|
711
|
-
tokens.transitions.duration.moderate; // "300ms"
|
|
712
|
-
tokens.transitions.duration.slow; // "500ms"
|
|
713
|
-
tokens.transitions.duration.slower; // "700ms"
|
|
160
|
+
- `@phcdevworks/spectre-tokens/index.css`
|
|
714
161
|
|
|
715
|
-
|
|
716
|
-
tokens.transitions.easing.linear; // "linear"
|
|
717
|
-
tokens.transitions.easing.in; // "cubic-bezier(0.4, 0, 1, 1)"
|
|
718
|
-
tokens.transitions.easing.out; // "cubic-bezier(0, 0, 0.2, 1)"
|
|
719
|
-
tokens.transitions.easing.inOut; // "cubic-bezier(0.4, 0, 0.2, 1)"
|
|
720
|
-
tokens.transitions.easing.spring; // "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
|
721
|
-
```
|
|
162
|
+
## Relationship to the rest of Spectre
|
|
722
163
|
|
|
723
|
-
|
|
164
|
+
Spectre keeps responsibilities separate:
|
|
724
165
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
tokens.animations.bounce; // { duration: "500ms", easing: "...", keyframes: "bounce" }
|
|
732
|
-
tokens.animations.shake; // { duration: "400ms", easing: "...", keyframes: "shake" }
|
|
733
|
-
tokens.animations.pulse; // { duration: "1500ms", easing: "...", keyframes: "pulse" }
|
|
734
|
-
```
|
|
166
|
+
- [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens)
|
|
167
|
+
defines visual language, semantic roles, and token contracts
|
|
168
|
+
- [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui) turns
|
|
169
|
+
those contracts into reusable CSS, Tailwind tooling, and shared styling
|
|
170
|
+
behavior
|
|
171
|
+
- Adapter packages translate Spectre contracts for framework-specific delivery
|
|
735
172
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
```ts
|
|
739
|
-
tokens.borders.card; // "#334155"
|
|
740
|
-
tokens.borders.input; // "#cbd5f5"
|
|
741
|
-
```
|
|
173
|
+
That separation keeps token meaning centralized while letting the package system
|
|
174
|
+
expand by responsibility.
|
|
742
175
|
|
|
743
|
-
##
|
|
176
|
+
## Development
|
|
744
177
|
|
|
745
|
-
|
|
746
|
-
| ---------- | ------------------------------------------------------------------------------------------------------------- |
|
|
747
|
-
| `tokens/` | Raw JSON token files owned by design (e.g. `core.json`, modes, component semantics). |
|
|
748
|
-
| `src/` | TypeScript source that turns JSON into reusable formats (JS/TS exports, Tailwind theme, CSS helpers). |
|
|
749
|
-
| `scripts/` | Build utilities. `build-css.js` consumes the compiled library and writes `dist/index.css`. |
|
|
750
|
-
| `dist/` | Generated artifacts: `index.js`, `index.cjs`, `index.d.ts`, and `index.css`. Regenerated via `npm run build`. |
|
|
751
|
-
|
|
752
|
-
Designers only touch the JSON under `tokens/`. Engineering evolves `src/` + `scripts/` when structure changes.
|
|
753
|
-
|
|
754
|
-
## Build & Release
|
|
178
|
+
Regenerate package outputs:
|
|
755
179
|
|
|
756
180
|
```bash
|
|
757
181
|
npm run build
|
|
758
182
|
```
|
|
759
183
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
> Do not hand-edit `dist/`. Always regenerate it via the build.
|
|
763
|
-
|
|
764
|
-
For release history and version notes, see the **[Changelog](CHANGELOG.md)**.
|
|
765
|
-
|
|
766
|
-
## Migration & Comparison Guide
|
|
767
|
-
|
|
768
|
-
### Migrating from Other Token Systems
|
|
769
|
-
|
|
770
|
-
#### From Style Dictionary / Design Tokens Format
|
|
771
|
-
|
|
772
|
-
```js
|
|
773
|
-
// Old: Style Dictionary format
|
|
774
|
-
{
|
|
775
|
-
"color": {
|
|
776
|
-
"brand": {
|
|
777
|
-
"primary": { "value": "#8652ff" }
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// New: Spectre Tokens
|
|
783
|
-
tokens.colors.brand["500"] // "#8652ff"
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
#### From Tailwind Default Theme
|
|
787
|
-
|
|
788
|
-
```js
|
|
789
|
-
// Old: Tailwind's theme
|
|
790
|
-
colors.purple[500]; // "#a855f7"
|
|
791
|
-
spacing[4]; // "1rem"
|
|
792
|
-
|
|
793
|
-
// New: Spectre Tokens
|
|
794
|
-
tokens.colors.brand["500"]; // "#8652ff" (custom brand purple)
|
|
795
|
-
tokens.space["16"]; // "1rem"
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
#### From CSS Custom Properties Only
|
|
799
|
-
|
|
800
|
-
```css
|
|
801
|
-
/* Old: Manual CSS variables */
|
|
802
|
-
:root {
|
|
803
|
-
--primary-color: #8652ff;
|
|
804
|
-
--spacing-4: 1rem;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/* New: Spectre auto-generated */
|
|
808
|
-
:root {
|
|
809
|
-
--sp-color-brand-500: #8652ff;
|
|
810
|
-
--sp-space-16: 1rem;
|
|
811
|
-
/* Plus 300+ more tokens */
|
|
812
|
-
}
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
### Key Differences from Other Systems
|
|
816
|
-
|
|
817
|
-
| Feature | Spectre Tokens | Tailwind | Material UI | Chakra UI |
|
|
818
|
-
| ----------------------- | ------------------------- | ------------------- | ------------------- | ----------------- |
|
|
819
|
-
| **Format** | JSON → JS/TS/CSS | JS Config | Theme Object | Theme Object |
|
|
820
|
-
| **CSS Variables** | ✅ Auto-generated | ⚠️ Opt-in (v4+) | ✅ Built-in | ✅ Built-in |
|
|
821
|
-
| **Dark Mode** | ✅ Via `modes` | ✅ Via classes | ✅ Via palette mode | ✅ Via color mode |
|
|
822
|
-
| **Type Safety** | ✅ Full TypeScript | ⚠️ Partial | ✅ Full | ✅ Full |
|
|
823
|
-
| **Framework Agnostic** | ✅ Yes | ⚠️ Tailwind-focused | ❌ React-only | ❌ React-only |
|
|
824
|
-
| **Semantic Tokens** | ✅ surface/text/component | ❌ Utility-first | ✅ Built-in | ✅ Built-in |
|
|
825
|
-
| **Button States** | ✅ CRO-optimized | ❌ DIY | ✅ Built-in | ✅ Built-in |
|
|
826
|
-
| **Form States** | ✅ Validation-aware | ❌ DIY | ✅ Built-in | ✅ Built-in |
|
|
827
|
-
| **Tailwind Compatible** | ✅ Preset provided | N/A | ⚠️ Via plugin | ⚠️ Via plugin |
|
|
828
|
-
|
|
829
|
-
### Why Choose Spectre Tokens?
|
|
830
|
-
|
|
831
|
-
1. **True Single Source of Truth**: JSON tokens drive everything - JS, CSS, Tailwind, documentation
|
|
832
|
-
2. **CRO-First Design**: Button and form states optimized for conversion and UX
|
|
833
|
-
3. **Framework Agnostic**: Use with React, Vue, Svelte, WordPress, Astro, 11ty, or vanilla JS
|
|
834
|
-
4. **Semantic + Utility**: Get both semantic tokens (surface, text) and utility tokens (colors, space)
|
|
835
|
-
5. **WCAG Built-In**: Accessibility constraints enforced at the token level
|
|
836
|
-
6. **No Lock-In**: Export to CSS variables and use anywhere, or integrate deeply with Tailwind
|
|
837
|
-
7. **Type-Safe**: Complete TypeScript definitions with autocomplete
|
|
838
|
-
|
|
839
|
-
## Spectre Design Philosophy
|
|
840
|
-
|
|
841
|
-
Spectre is a **specification-driven design system** built on three strict layers:
|
|
842
|
-
|
|
843
|
-
### 1. @phcdevworks/spectre-tokens (Foundation)
|
|
844
|
-
|
|
845
|
-
**Purpose**: Single source of truth for design values (colors, surfaces, text roles, space, radii, shadows, etc.)
|
|
846
|
-
|
|
847
|
-
**Exports**: CSS variables (`--sp-*`), TypeScript token object, Tailwind-compatible theme mappings
|
|
848
|
-
|
|
849
|
-
**Rules**:
|
|
850
|
-
|
|
851
|
-
- Tokens define semantic meaning, not UI behavior
|
|
852
|
-
- UI must never invent new colors or values
|
|
853
|
-
- Designers own `tokens/*.json`; engineers maintain `src/` transforms
|
|
854
|
-
- Contrast targets and accessibility constraints are encoded at the token level
|
|
855
|
-
|
|
856
|
-
**Status**: v0.1.0 released with stable semantic roles (`surface.*`, `text.*`, `component.*`) and considered correct/locked
|
|
857
|
-
|
|
858
|
-
### 2. @phcdevworks/spectre-ui (Framework-Agnostic UI Layer)
|
|
859
|
-
|
|
860
|
-
**Purpose**: Converts tokens into real CSS and class recipes
|
|
861
|
-
|
|
862
|
-
**Ships**:
|
|
863
|
-
|
|
864
|
-
- `index.css` (canonical CSS bundle: tokens + base + components + utilities)
|
|
865
|
-
- `base.css` (resets + globals)
|
|
866
|
-
- `components.css` (`.sp-btn`, `.sp-card`, `.sp-input`, etc.)
|
|
867
|
-
- `utilities.css` (`.sp-stack`, `.sp-container`, etc.)
|
|
868
|
-
- Type-safe recipes: `getButtonClasses`, `getCardClasses`, `getInputClasses`
|
|
869
|
-
|
|
870
|
-
**Rules**:
|
|
871
|
-
|
|
872
|
-
- UI must consume tokens, not redefine design values
|
|
873
|
-
- Literal values in CSS are fallbacks only
|
|
874
|
-
- Every CSS selector has a matching recipe where applicable
|
|
875
|
-
- Tailwind preset is optional and non-authoritative
|
|
876
|
-
|
|
877
|
-
**Status**: v0.1.0 released, hardened and aligned to tokens
|
|
878
|
-
|
|
879
|
-
### 3. Adapters (WordPress, Astro, etc.)
|
|
880
|
-
|
|
881
|
-
**Purpose**: Thin framework wrappers around spectre-ui; automatically sync and load the Spectre UI CSS bundle
|
|
882
|
-
|
|
883
|
-
**Rules**:
|
|
884
|
-
|
|
885
|
-
- Adapters never define styles, never duplicate CSS, never load tokens directly
|
|
886
|
-
- All design values come from tokens, all CSS comes from spectre-ui
|
|
887
|
-
- Adapters only translate and integrate
|
|
888
|
-
|
|
889
|
-
### Golden Rule (Non-Negotiable)
|
|
890
|
-
|
|
891
|
-
**Tokens define meaning. UI defines structure. Adapters only translate.**
|
|
892
|
-
|
|
893
|
-
- If it's a design token → belongs in `@phcdevworks/spectre-tokens`
|
|
894
|
-
- If it's a CSS class or style → belongs in `@phcdevworks/spectre-ui`
|
|
895
|
-
- If it's framework integration → belongs in an adapter
|
|
896
|
-
|
|
897
|
-
## Design Principles
|
|
898
|
-
|
|
899
|
-
1. **Single source of truth** – Tokens originate in JSON and flow into every runtime surface.
|
|
900
|
-
2. **Format-agnostic** – Consumers choose JS objects, CSS variables, Tailwind presets, or generated strings.
|
|
901
|
-
3. **Framework-neutral** – Works in WordPress, Astro, 11ty, React, or any environment that can ingest CSS/JS.
|
|
902
|
-
4. **Accessibility-first** – Focus rings, touch targets, and contrast targets are encoded directly in tokens.
|
|
903
|
-
5. **CRO-aware** – Buttons, forms, and state tokens are tuned for real-world conversion funnels.
|
|
904
|
-
|
|
905
|
-
## TypeScript Support
|
|
906
|
-
|
|
907
|
-
Type definitions are bundled automatically with comprehensive interfaces for all token types:
|
|
908
|
-
|
|
909
|
-
```ts
|
|
910
|
-
import type {
|
|
911
|
-
Tokens,
|
|
912
|
-
SpectreTokens,
|
|
913
|
-
TailwindTheme,
|
|
914
|
-
ColorScale,
|
|
915
|
-
TokenScale,
|
|
916
|
-
ButtonStateTokens,
|
|
917
|
-
FormStateTokens,
|
|
918
|
-
TypographyTokens,
|
|
919
|
-
TypographyScaleEntry,
|
|
920
|
-
FontScaleEntry,
|
|
921
|
-
TransitionTokens,
|
|
922
|
-
AnimationEntry,
|
|
923
|
-
AccessibilityTokens,
|
|
924
|
-
ComponentTokens,
|
|
925
|
-
ComponentBadgeTokens,
|
|
926
|
-
ComponentIconBoxTokens,
|
|
927
|
-
LayoutTokens,
|
|
928
|
-
SpectreModeTokens,
|
|
929
|
-
SpectreModeName,
|
|
930
|
-
SemanticTokenValue,
|
|
931
|
-
CssVariableOptions,
|
|
932
|
-
CssVariableMap,
|
|
933
|
-
} from "@phcdevworks/spectre-tokens";
|
|
934
|
-
|
|
935
|
-
const allTokens: SpectreTokens = tokens;
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
### Key Type Interfaces
|
|
939
|
-
|
|
940
|
-
**Core Token Types:**
|
|
941
|
-
|
|
942
|
-
```ts
|
|
943
|
-
// Color and token scales
|
|
944
|
-
type ColorScale = Record<string, string>;
|
|
945
|
-
type TokenScale = Record<string, string>;
|
|
946
|
-
|
|
947
|
-
// Typography
|
|
948
|
-
interface TypographyScaleEntry {
|
|
949
|
-
fontSize: string;
|
|
950
|
-
lineHeight: string;
|
|
951
|
-
fontWeight?: number;
|
|
952
|
-
letterSpacing?: string;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
interface FontScaleEntry {
|
|
956
|
-
size: string;
|
|
957
|
-
lineHeight: string;
|
|
958
|
-
weight: number;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Component states
|
|
962
|
-
interface ButtonStateTokens {
|
|
963
|
-
bg: string;
|
|
964
|
-
bgHover: string;
|
|
965
|
-
bgActive: string;
|
|
966
|
-
bgDisabled: string;
|
|
967
|
-
text: string;
|
|
968
|
-
textDisabled: string;
|
|
969
|
-
border?: string;
|
|
970
|
-
borderDisabled?: string;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
interface FormStateTokens {
|
|
974
|
-
bg?: string;
|
|
975
|
-
border: string;
|
|
976
|
-
text?: string;
|
|
977
|
-
placeholder?: string;
|
|
978
|
-
ring?: string;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Component tokens
|
|
982
|
-
interface ComponentBadgeTokens<Value = string> {
|
|
983
|
-
neutralBg: Value;
|
|
984
|
-
neutralText: Value;
|
|
985
|
-
infoBg: Value;
|
|
986
|
-
infoText: Value;
|
|
987
|
-
successBg: Value;
|
|
988
|
-
successText: Value;
|
|
989
|
-
warningBg: Value;
|
|
990
|
-
warningText: Value;
|
|
991
|
-
dangerBg: Value;
|
|
992
|
-
dangerText: Value;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
interface ComponentIconBoxTokens<Value = string> {
|
|
996
|
-
bg: Value;
|
|
997
|
-
border: Value;
|
|
998
|
-
iconDefault: Value;
|
|
999
|
-
iconSuccess: Value;
|
|
1000
|
-
iconWarning: Value;
|
|
1001
|
-
iconDanger: Value;
|
|
1002
|
-
}
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
**Semantic Token Types:**
|
|
1006
|
-
|
|
1007
|
-
```ts
|
|
1008
|
-
// Semantic token values can be strings or objects with metadata
|
|
1009
|
-
type SemanticTokenValue = string | { value: string; [key: string]: any };
|
|
1010
|
-
|
|
1011
|
-
// Mode names
|
|
1012
|
-
type SpectreModeName = "default" | "dark";
|
|
1013
|
-
|
|
1014
|
-
// Mode structure
|
|
1015
|
-
interface SpectreModeTokens {
|
|
1016
|
-
surface: {
|
|
1017
|
-
page: SemanticTokenValue;
|
|
1018
|
-
card: SemanticTokenValue;
|
|
1019
|
-
input: SemanticTokenValue;
|
|
1020
|
-
overlay: SemanticTokenValue;
|
|
1021
|
-
};
|
|
1022
|
-
text: {
|
|
1023
|
-
onPage: {
|
|
1024
|
-
default: SemanticTokenValue;
|
|
1025
|
-
muted: SemanticTokenValue;
|
|
1026
|
-
subtle: SemanticTokenValue;
|
|
1027
|
-
meta: SemanticTokenValue;
|
|
1028
|
-
};
|
|
1029
|
-
onSurface: {
|
|
1030
|
-
/* same as onPage */
|
|
1031
|
-
};
|
|
1032
|
-
};
|
|
1033
|
-
component: ComponentTokens<SemanticTokenValue>;
|
|
1034
|
-
}
|
|
1035
|
-
```
|
|
1036
|
-
|
|
1037
|
-
**CSS Variable Generation:**
|
|
1038
|
-
|
|
1039
|
-
```ts
|
|
1040
|
-
interface CssVariableOptions {
|
|
1041
|
-
prefix?: string; // Default: "sp"
|
|
1042
|
-
selector?: string; // Default: ":root"
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
type CssVariableMap = Record<string, string>;
|
|
1046
|
-
|
|
1047
|
-
// Usage
|
|
1048
|
-
const css: string = generateCssVariables(tokens, {
|
|
1049
|
-
prefix: "spectre",
|
|
1050
|
-
selector: ".my-theme",
|
|
1051
|
-
});
|
|
1052
|
-
```
|
|
1053
|
-
|
|
1054
|
-
### Type-Safe Token Access
|
|
1055
|
-
|
|
1056
|
-
TypeScript provides full autocomplete and type checking:
|
|
1057
|
-
|
|
1058
|
-
```ts
|
|
1059
|
-
import tokens from "@phcdevworks/spectre-tokens";
|
|
1060
|
-
|
|
1061
|
-
// Autocomplete for all color palettes and shades
|
|
1062
|
-
const brandColor = tokens.colors.brand["500"]; // ✅ Type: string
|
|
1063
|
-
|
|
1064
|
-
// Autocomplete for button states
|
|
1065
|
-
const primaryBg = tokens.buttons.primary.bg; // ✅ Type: string
|
|
1066
|
-
const primaryHover = tokens.buttons.primary.bgHover; // ✅ Type: string
|
|
1067
|
-
|
|
1068
|
-
// Type checking prevents invalid access
|
|
1069
|
-
// tokens.colors.invalid["500"]; // ❌ TypeScript error
|
|
1070
|
-
// tokens.buttons.primary.invalidProp; // ❌ TypeScript error
|
|
1071
|
-
|
|
1072
|
-
// Full type support for component tokens
|
|
1073
|
-
const badgeBg: string = tokens.component.badge.successBg;
|
|
1074
|
-
const iconColor: string = tokens.component.iconBox.iconDanger;
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
## Part of the Spectre Suite
|
|
1078
|
-
|
|
1079
|
-
- **Spectre Tokens** – Design-token foundation (this package)
|
|
1080
|
-
- **Spectre UI** – Core styling layer
|
|
1081
|
-
- **Spectre Blocks** – WordPress block library
|
|
1082
|
-
- **Spectre Astro** – Astro integration
|
|
1083
|
-
- **Spectre 11ty** – Eleventy integration
|
|
1084
|
-
|
|
1085
|
-
## Practical Examples
|
|
1086
|
-
|
|
1087
|
-
### Building a Button Component
|
|
1088
|
-
|
|
1089
|
-
```ts
|
|
1090
|
-
import tokens from "@phcdevworks/spectre-tokens";
|
|
1091
|
-
|
|
1092
|
-
// Create a type-safe button configuration
|
|
1093
|
-
const buttonStyles = {
|
|
1094
|
-
primary: {
|
|
1095
|
-
background: tokens.buttons.primary.bg,
|
|
1096
|
-
color: tokens.buttons.primary.text,
|
|
1097
|
-
borderRadius: tokens.radii.md,
|
|
1098
|
-
padding: `${tokens.space["12"]} ${tokens.space["24"]}`,
|
|
1099
|
-
fontSize: tokens.font.md.size,
|
|
1100
|
-
fontWeight: tokens.font.md.weight,
|
|
1101
|
-
transition: `all ${tokens.transitions.duration.fast} ${tokens.transitions.easing.out}`,
|
|
1102
|
-
"&:hover": {
|
|
1103
|
-
background: tokens.buttons.primary.bgHover,
|
|
1104
|
-
},
|
|
1105
|
-
"&:active": {
|
|
1106
|
-
background: tokens.buttons.primary.bgActive,
|
|
1107
|
-
},
|
|
1108
|
-
"&:disabled": {
|
|
1109
|
-
background: tokens.buttons.primary.bgDisabled,
|
|
1110
|
-
color: tokens.buttons.primary.textDisabled,
|
|
1111
|
-
},
|
|
1112
|
-
},
|
|
1113
|
-
};
|
|
1114
|
-
```
|
|
1115
|
-
|
|
1116
|
-
### Creating a Card Layout
|
|
1117
|
-
|
|
1118
|
-
```css
|
|
1119
|
-
.card {
|
|
1120
|
-
background: var(--sp-surface-card);
|
|
1121
|
-
border: 1px solid var(--sp-borders-card);
|
|
1122
|
-
border-radius: var(--sp-radius-lg);
|
|
1123
|
-
padding: var(--sp-space-24);
|
|
1124
|
-
box-shadow: var(--sp-shadow-md);
|
|
1125
|
-
|
|
1126
|
-
/* Semantic layout spacing */
|
|
1127
|
-
display: flex;
|
|
1128
|
-
flex-direction: column;
|
|
1129
|
-
gap: var(--sp-layout-stack-gap-md);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
.card__title {
|
|
1133
|
-
color: var(--sp-text-on-surface-default);
|
|
1134
|
-
font-size: var(--sp-font-lg-size);
|
|
1135
|
-
line-height: var(--sp-font-lg-line-height);
|
|
1136
|
-
font-weight: var(--sp-font-lg-weight);
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
.card__description {
|
|
1140
|
-
color: var(--sp-text-on-surface-muted);
|
|
1141
|
-
font-size: var(--sp-font-md-size);
|
|
1142
|
-
line-height: var(--sp-font-md-line-height);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
.card__meta {
|
|
1146
|
-
color: var(--sp-text-on-surface-meta);
|
|
1147
|
-
font-size: var(--sp-font-sm-size);
|
|
1148
|
-
}
|
|
1149
|
-
```
|
|
1150
|
-
|
|
1151
|
-
### Form with Validation States
|
|
1152
|
-
|
|
1153
|
-
```html
|
|
1154
|
-
<!-- HTML structure -->
|
|
1155
|
-
<div class="form-field">
|
|
1156
|
-
<label class="form-label">Email Address</label>
|
|
1157
|
-
<input type="email" class="form-input" />
|
|
1158
|
-
<span class="form-hint">We'll never share your email.</span>
|
|
1159
|
-
</div>
|
|
1160
|
-
|
|
1161
|
-
<div class="form-field form-field--error">
|
|
1162
|
-
<label class="form-label">Password</label>
|
|
1163
|
-
<input type="password" class="form-input" />
|
|
1164
|
-
<span class="form-error">Password must be at least 8 characters.</span>
|
|
1165
|
-
</div>
|
|
1166
|
-
```
|
|
1167
|
-
|
|
1168
|
-
```css
|
|
1169
|
-
/* CSS using Spectre tokens */
|
|
1170
|
-
.form-field {
|
|
1171
|
-
display: flex;
|
|
1172
|
-
flex-direction: column;
|
|
1173
|
-
gap: var(--sp-layout-stack-gap-sm);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
.form-label {
|
|
1177
|
-
color: var(--sp-text-on-page-default);
|
|
1178
|
-
font-size: var(--sp-font-sm-size);
|
|
1179
|
-
font-weight: var(--sp-font-md-weight);
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
.form-input {
|
|
1183
|
-
background: var(--sp-form-default-bg);
|
|
1184
|
-
border: 1px solid var(--sp-form-default-border);
|
|
1185
|
-
border-radius: var(--sp-radius-md);
|
|
1186
|
-
padding: var(--sp-space-12) var(--sp-space-16);
|
|
1187
|
-
font-size: var(--sp-font-md-size);
|
|
1188
|
-
color: var(--sp-form-default-text);
|
|
1189
|
-
transition: border-color var(--sp-transition-duration-fast)
|
|
1190
|
-
var(--sp-transition-easing-out);
|
|
1191
|
-
min-height: var(--sp-accessibility-min-touch-target);
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
.form-input::placeholder {
|
|
1195
|
-
color: var(--sp-form-default-placeholder);
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
.form-input:hover {
|
|
1199
|
-
border-color: var(--sp-form-hover-border);
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
.form-input:focus {
|
|
1203
|
-
outline: none;
|
|
1204
|
-
border-color: var(--sp-form-focus-border);
|
|
1205
|
-
box-shadow: 0 0 0 var(--sp-accessibility-focus-ring-width)
|
|
1206
|
-
var(--sp-form-focus-ring);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
.form-field--error .form-input {
|
|
1210
|
-
border-color: var(--sp-form-invalid-border);
|
|
1211
|
-
background: var(--sp-form-invalid-bg);
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
.form-hint {
|
|
1215
|
-
color: var(--sp-text-on-page-subtle);
|
|
1216
|
-
font-size: var(--sp-font-xs-size);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
.form-error {
|
|
1220
|
-
color: var(--sp-form-invalid-text);
|
|
1221
|
-
font-size: var(--sp-font-xs-size);
|
|
1222
|
-
}
|
|
1223
|
-
```
|
|
1224
|
-
|
|
1225
|
-
### Responsive Layout with Breakpoints
|
|
1226
|
-
|
|
1227
|
-
```ts
|
|
1228
|
-
import { tailwindTheme } from "@phcdevworks/spectre-tokens";
|
|
1229
|
-
|
|
1230
|
-
// Use in CSS-in-JS or styled-components
|
|
1231
|
-
const Container = styled.div`
|
|
1232
|
-
padding-inline: ${tokens.layout.container.paddingInline.sm};
|
|
1233
|
-
|
|
1234
|
-
@media (min-width: ${tokens.breakpoints.md}) {
|
|
1235
|
-
padding-inline: ${tokens.layout.container.paddingInline.md};
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
@media (min-width: ${tokens.breakpoints.lg}) {
|
|
1239
|
-
padding-inline: ${tokens.layout.container.paddingInline.lg};
|
|
1240
|
-
}
|
|
1241
|
-
`;
|
|
1242
|
-
```
|
|
1243
|
-
|
|
1244
|
-
### Animated Modal
|
|
1245
|
-
|
|
1246
|
-
```css
|
|
1247
|
-
.modal {
|
|
1248
|
-
position: fixed;
|
|
1249
|
-
inset: 0;
|
|
1250
|
-
z-index: var(--sp-z-index-modal);
|
|
1251
|
-
display: flex;
|
|
1252
|
-
align-items: center;
|
|
1253
|
-
justify-content: center;
|
|
1254
|
-
background: var(--sp-surface-overlay);
|
|
1255
|
-
animation: fade-in var(--sp-animation-fade-in-duration)
|
|
1256
|
-
var(--sp-animation-fade-in-easing);
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
.modal__content {
|
|
1260
|
-
background: var(--sp-surface-card);
|
|
1261
|
-
border-radius: var(--sp-radius-lg);
|
|
1262
|
-
padding: var(--sp-space-32);
|
|
1263
|
-
box-shadow: var(--sp-shadow-lg);
|
|
1264
|
-
max-width: 32rem;
|
|
1265
|
-
animation: scale-in var(--sp-animation-scale-in-duration)
|
|
1266
|
-
var(--sp-animation-scale-in-easing);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
@keyframes fade-in {
|
|
1270
|
-
from {
|
|
1271
|
-
opacity: 0;
|
|
1272
|
-
}
|
|
1273
|
-
to {
|
|
1274
|
-
opacity: 1;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
@keyframes scale-in {
|
|
1279
|
-
from {
|
|
1280
|
-
opacity: 0;
|
|
1281
|
-
transform: scale(0.95);
|
|
1282
|
-
}
|
|
1283
|
-
to {
|
|
1284
|
-
opacity: 1;
|
|
1285
|
-
transform: scale(1);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
### Badge Component System
|
|
1291
|
-
|
|
1292
|
-
```tsx
|
|
1293
|
-
// React/TypeScript example
|
|
1294
|
-
import tokens from "@phcdevworks/spectre-tokens";
|
|
1295
|
-
|
|
1296
|
-
type BadgeVariant = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
|
1297
|
-
|
|
1298
|
-
interface BadgeProps {
|
|
1299
|
-
variant?: BadgeVariant;
|
|
1300
|
-
children: React.ReactNode;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
const Badge: React.FC<BadgeProps> = ({ variant = 'neutral', children }) => {
|
|
1304
|
-
const styles = {
|
|
1305
|
-
background: tokens.component.badge[`${variant}Bg` as keyof typeof tokens.component.badge],
|
|
1306
|
-
color: tokens.component.badge[`${variant}Text` as keyof typeof tokens.component.badge],
|
|
1307
|
-
padding: `${tokens.space["4"]} ${tokens.space["8"]}`,
|
|
1308
|
-
borderRadius: tokens.radii.sm,
|
|
1309
|
-
fontSize: tokens.font.xs.size,
|
|
1310
|
-
fontWeight: tokens.font.xs.weight,
|
|
1311
|
-
display: 'inline-flex',
|
|
1312
|
-
alignItems: 'center',
|
|
1313
|
-
};
|
|
1314
|
-
|
|
1315
|
-
return <span style={styles}>{children}</span>;
|
|
1316
|
-
};
|
|
1317
|
-
|
|
1318
|
-
// Usage
|
|
1319
|
-
<Badge variant="success">Active</Badge>
|
|
1320
|
-
<Badge variant="warning">Pending</Badge>
|
|
1321
|
-
<Badge variant="danger">Error</Badge>
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
### Custom Theme Scope
|
|
1325
|
-
|
|
1326
|
-
```ts
|
|
1327
|
-
import { generateCssVariables } from "@phcdevworks/spectre-tokens";
|
|
1328
|
-
|
|
1329
|
-
// Generate scoped CSS variables for a specific component
|
|
1330
|
-
const customThemeCSS = generateCssVariables(tokens, {
|
|
1331
|
-
selector: ".custom-widget",
|
|
1332
|
-
prefix: "widget",
|
|
1333
|
-
});
|
|
1334
|
-
|
|
1335
|
-
// Output: Variables scoped to .custom-widget with --widget-* prefix
|
|
1336
|
-
// .custom-widget {
|
|
1337
|
-
// --widget-color-brand-500: #8652ff;
|
|
1338
|
-
// --widget-space-16: 1rem;
|
|
1339
|
-
// ...
|
|
1340
|
-
// }
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
## Troubleshooting & FAQ
|
|
1344
|
-
|
|
1345
|
-
### CSS Variables Not Working
|
|
1346
|
-
|
|
1347
|
-
**Problem:** CSS variables like `var(--sp-color-brand-500)` are not applying.
|
|
1348
|
-
|
|
1349
|
-
**Solution:**
|
|
1350
|
-
|
|
1351
|
-
1. Ensure you've imported the CSS file:
|
|
1352
|
-
```css
|
|
1353
|
-
@import "@phcdevworks/spectre-tokens/dist/index.css";
|
|
1354
|
-
```
|
|
1355
|
-
2. Check that the file is being served correctly from `node_modules/`
|
|
1356
|
-
3. Verify CSS import order - token CSS should come before your custom styles
|
|
1357
|
-
|
|
1358
|
-
### TypeScript Types Not Found
|
|
1359
|
-
|
|
1360
|
-
**Problem:** TypeScript can't find type definitions.
|
|
1361
|
-
|
|
1362
|
-
**Solution:**
|
|
1363
|
-
|
|
1364
|
-
1. Check that `@phcdevworks/spectre-tokens` is in your dependencies (not devDependencies)
|
|
1365
|
-
2. Verify `dist/index.d.ts` exists in your `node_modules`
|
|
1366
|
-
3. Try clearing TypeScript cache: `rm -rf node_modules/.cache`
|
|
1367
|
-
4. Restart your TypeScript server/IDE
|
|
1368
|
-
|
|
1369
|
-
### Dark Mode Not Switching
|
|
1370
|
-
|
|
1371
|
-
**Problem:** Setting `data-spectre-theme="dark"` doesn't change colors.
|
|
1372
|
-
|
|
1373
|
-
**Solution:**
|
|
1374
|
-
|
|
1375
|
-
1. Apply the attribute to the `:root` or `<html>` element:
|
|
1376
|
-
```js
|
|
1377
|
-
document.documentElement.setAttribute("data-spectre-theme", "dark");
|
|
1378
|
-
```
|
|
1379
|
-
2. Ensure you're using semantic tokens (surface, text, component) not raw colors
|
|
1380
|
-
3. Raw color tokens (e.g., `tokens.colors.brand[500]`) don't change with themes - use semantic tokens instead
|
|
1381
|
-
|
|
1382
|
-
### Tailwind Integration Issues
|
|
1383
|
-
|
|
1384
|
-
**Problem:** Tailwind utilities not using Spectre tokens.
|
|
1385
|
-
|
|
1386
|
-
**Solution:**
|
|
1387
|
-
|
|
1388
|
-
1. Verify preset is added to your config:
|
|
1389
|
-
|
|
1390
|
-
```ts
|
|
1391
|
-
import { tailwindPreset } from "@phcdevworks/spectre-tokens";
|
|
1392
|
-
|
|
1393
|
-
export default {
|
|
1394
|
-
presets: [tailwindPreset], // Must be in presets, not theme.extend
|
|
1395
|
-
};
|
|
1396
|
-
```
|
|
1397
|
-
|
|
1398
|
-
2. Clear Tailwind cache: `rm -rf .next/cache` or `rm -rf node_modules/.cache`
|
|
1399
|
-
3. Rebuild: `npm run build`
|
|
1400
|
-
|
|
1401
|
-
### Difference Between `font` and `typography`?
|
|
1402
|
-
|
|
1403
|
-
**Question:** When should I use `tokens.font.*` vs `tokens.typography.*`?
|
|
1404
|
-
|
|
1405
|
-
**Answer:**
|
|
1406
|
-
|
|
1407
|
-
- **Use `tokens.font.*`** for quick access to size, line-height, and weight:
|
|
1408
|
-
```ts
|
|
1409
|
-
tokens.font.md.size; // "1rem"
|
|
1410
|
-
tokens.font.md.lineHeight; // "1.75rem"
|
|
1411
|
-
tokens.font.md.weight; // 500
|
|
1412
|
-
```
|
|
1413
|
-
- **Use `tokens.typography.*`** when you need:
|
|
1414
|
-
- Font families: `tokens.typography.families.sans`
|
|
1415
|
-
- Letter spacing: `tokens.typography.scale.xs.letterSpacing`
|
|
1416
|
-
- Complete typographic object: `tokens.typography.scale.md`
|
|
1417
|
-
|
|
1418
|
-
### How to Create Custom Modes?
|
|
1419
|
-
|
|
1420
|
-
**Question:** Can I add my own theme modes beyond default/dark?
|
|
1421
|
-
|
|
1422
|
-
**Answer:**
|
|
1423
|
-
Yes! Extend the `modes` object in your `core.json`:
|
|
184
|
+
Run validation checks:
|
|
1424
185
|
|
|
1425
|
-
```
|
|
1426
|
-
|
|
1427
|
-
"modes": {
|
|
1428
|
-
"default": {
|
|
1429
|
-
/* ... */
|
|
1430
|
-
},
|
|
1431
|
-
"dark": {
|
|
1432
|
-
/* ... */
|
|
1433
|
-
},
|
|
1434
|
-
"highContrast": {
|
|
1435
|
-
"surface": {
|
|
1436
|
-
"page": { "value": "#ffffff" }
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
```
|
|
1442
|
-
|
|
1443
|
-
Then generate CSS with:
|
|
1444
|
-
|
|
1445
|
-
```css
|
|
1446
|
-
:root[data-spectre-theme="highContrast"] {
|
|
1447
|
-
/* Custom mode variables */
|
|
1448
|
-
}
|
|
1449
|
-
```
|
|
1450
|
-
|
|
1451
|
-
### Can I Override Specific Tokens?
|
|
1452
|
-
|
|
1453
|
-
**Question:** How do I override just a few tokens without forking?
|
|
1454
|
-
|
|
1455
|
-
**Answer:**
|
|
1456
|
-
Two approaches:
|
|
1457
|
-
|
|
1458
|
-
1. **CSS Override** (simplest):
|
|
1459
|
-
|
|
1460
|
-
```css
|
|
1461
|
-
:root {
|
|
1462
|
-
--sp-color-brand-500: #your-color !important;
|
|
1463
|
-
}
|
|
1464
|
-
```
|
|
1465
|
-
|
|
1466
|
-
2. **JavaScript Override**:
|
|
1467
|
-
|
|
1468
|
-
```ts
|
|
1469
|
-
import tokens from "@phcdevworks/spectre-tokens";
|
|
1470
|
-
|
|
1471
|
-
const customTokens = {
|
|
1472
|
-
...tokens,
|
|
1473
|
-
colors: {
|
|
1474
|
-
...tokens.colors,
|
|
1475
|
-
brand: {
|
|
1476
|
-
...tokens.colors.brand,
|
|
1477
|
-
"500": "#your-color",
|
|
1478
|
-
},
|
|
1479
|
-
},
|
|
1480
|
-
};
|
|
1481
|
-
```
|
|
1482
|
-
|
|
1483
|
-
### Accessing Nested Token Values
|
|
1484
|
-
|
|
1485
|
-
**Question:** Some tokens return `{ value: "..." }` instead of strings. Why?
|
|
1486
|
-
|
|
1487
|
-
**Answer:**
|
|
1488
|
-
Semantic tokens in `modes` use the `{ value: "..." }` format for metadata. The TypeScript library automatically resolves these:
|
|
1489
|
-
|
|
1490
|
-
```ts
|
|
1491
|
-
// In core.json: { "value": "#f8fafc" }
|
|
1492
|
-
tokens.surface.page; // Returns "#f8fafc" (string)
|
|
1493
|
-
|
|
1494
|
-
// The library handles resolution automatically
|
|
186
|
+
```bash
|
|
187
|
+
npm run check
|
|
1495
188
|
```
|
|
1496
189
|
|
|
1497
|
-
|
|
190
|
+
Key source areas:
|
|
1498
191
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
192
|
+
- `tokens/` for source token data
|
|
193
|
+
- `src/generated/` for generated token output
|
|
194
|
+
- `src/` for package entry points, CSS generation, and types
|
|
195
|
+
- `scripts/` for build and validation scripts
|
|
196
|
+
- `example/` for usage examples
|
|
1502
197
|
|
|
1503
198
|
## Contributing
|
|
1504
199
|
|
|
1505
|
-
|
|
200
|
+
PHCDevworks maintains this package as part of the Spectre system.
|
|
1506
201
|
|
|
1507
|
-
|
|
202
|
+
When contributing:
|
|
1508
203
|
|
|
1509
|
-
|
|
204
|
+
- treat `tokens/` as the source of truth
|
|
205
|
+
- keep generated outputs derived from source data
|
|
206
|
+
- avoid breaking token contracts without an intentional major-version change
|
|
207
|
+
- run `npm run build` and `npm run check` before opening a pull request
|
|
1510
208
|
|
|
1511
|
-
See
|
|
209
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.
|
|
1512
210
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
## ❤️ Support Spectre
|
|
1516
|
-
|
|
1517
|
-
If Spectre Tokens helps your workflow, consider sponsoring:
|
|
211
|
+
## License
|
|
1518
212
|
|
|
1519
|
-
|
|
1520
|
-
- [Buy Me a Coffee](https://buymeacoffee.com/phcdevworks)
|
|
213
|
+
MIT © PHCDevworks. See [LICENSE](LICENSE).
|