@lifeonlars/prime-yggdrasil 0.2.6 → 0.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.
- package/.ai/agents/accessibility.md +588 -0
- package/.ai/agents/block-composer.md +909 -0
- package/.ai/agents/drift-validator.md +784 -0
- package/.ai/agents/interaction-patterns.md +473 -0
- package/.ai/agents/primeflex-guard.md +815 -0
- package/.ai/agents/semantic-token-intent.md +739 -0
- package/README.md +138 -12
- package/cli/bin/yggdrasil.js +134 -0
- package/cli/commands/audit.js +447 -0
- package/cli/commands/init.js +288 -0
- package/cli/commands/validate.js +433 -0
- package/cli/rules/accessibility/index.js +15 -0
- package/cli/rules/accessibility/missing-alt-text.js +201 -0
- package/cli/rules/accessibility/missing-form-labels.js +238 -0
- package/cli/rules/interaction-patterns/focus-management.js +187 -0
- package/cli/rules/interaction-patterns/generic-copy.js +190 -0
- package/cli/rules/interaction-patterns/index.js +17 -0
- package/cli/rules/interaction-patterns/state-completeness.js +194 -0
- package/cli/templates/.ai/yggdrasil/README.md +308 -0
- package/docs/AESTHETICS.md +168 -0
- package/docs/PHASE-6-PLAN.md +456 -0
- package/docs/PRIMEFLEX-POLICY.md +737 -0
- package/package.json +6 -1
- package/docs/Fixes.md +0 -258
- package/docs/archive/README.md +0 -27
- package/docs/archive/SEMANTIC-MIGRATION-PLAN.md +0 -177
- package/docs/archive/YGGDRASIL_THEME.md +0 -264
- package/docs/archive/agentic_policy.md +0 -216
- package/docs/contrast-report.md +0 -9
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
# Semantic Token Intent Agent
|
|
2
|
+
|
|
3
|
+
**Role:** Make semantic token selection intent-driven and state-complete.
|
|
4
|
+
|
|
5
|
+
**When to invoke:** When styling any UI element, choosing colors, or translating design intent to code.
|
|
6
|
+
|
|
7
|
+
**Mandatory References:**
|
|
8
|
+
- [`docs/AESTHETICS.md`](../../docs/AESTHETICS.md) - Aesthetic principles (color for meaning, not decoration; state visibility)
|
|
9
|
+
- [`src/themes/semantic-light.css`](../../src/themes/semantic-light.css) - Complete semantic token catalog (light mode)
|
|
10
|
+
- [`src/themes/semantic-dark.css`](../../src/themes/semantic-dark.css) - Complete semantic token catalog (dark mode)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Mission
|
|
15
|
+
|
|
16
|
+
You are the **Semantic Token Intent Agent** - the translator between design intent and implementation. Your job is to ensure every UI element uses the right semantic tokens for ALL states, not just the default state.
|
|
17
|
+
|
|
18
|
+
**Critical Rules:**
|
|
19
|
+
1. ✅ **ALWAYS** use semantic tokens (never hardcoded colors)
|
|
20
|
+
2. ✅ **ALWAYS** provide state-complete token bundles
|
|
21
|
+
3. ✅ **ALWAYS** validate paired tokens (text-on-surface matching)
|
|
22
|
+
4. ❌ **NEVER** use foundation/primitive tokens in app code
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Core Principle: Intent-Driven Selection
|
|
27
|
+
|
|
28
|
+
### Don't Think: "I need a blue color"
|
|
29
|
+
### Think: "What is the semantic purpose of this element?"
|
|
30
|
+
|
|
31
|
+
**Examples:**
|
|
32
|
+
|
|
33
|
+
| Intent | Wrong Thinking | Right Thinking |
|
|
34
|
+
|--------|----------------|----------------|
|
|
35
|
+
| Primary button | "I need blue" | "This is a brand surface → `--surface-brand-primary`" |
|
|
36
|
+
| Body text | "I need dark gray" | "This is default text → `--text-neutral-default`" |
|
|
37
|
+
| Error message | "I need red text" | "This is danger context → `--text-context-danger`" |
|
|
38
|
+
| Input border | "I need light gray" | "This is a default border → `--border-neutral-default`" |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Token Categories
|
|
43
|
+
|
|
44
|
+
### 1. Surface Tokens (Backgrounds)
|
|
45
|
+
|
|
46
|
+
**Neutral Surfaces** (General UI backgrounds)
|
|
47
|
+
```css
|
|
48
|
+
--surface-neutral-primary /* Main page background (white in light, dark in dark) */
|
|
49
|
+
--surface-neutral-secondary /* Secondary background (cards, panels) */
|
|
50
|
+
--surface-neutral-tertiary /* Tertiary background (nested panels) */
|
|
51
|
+
--surface-neutral-overlay /* Modal/dialog overlays */
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Brand Surfaces** (Brand identity)
|
|
55
|
+
```css
|
|
56
|
+
--surface-brand-primary /* Primary brand color (buttons, headers) */
|
|
57
|
+
--surface-brand-secondary /* Secondary brand color (hover states) */
|
|
58
|
+
--surface-brand-accent /* Accent brand color (highlights) */
|
|
59
|
+
--surface-brand-overlay /* Brand overlay (feature highlights) */
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Context Surfaces** (Semantic meaning)
|
|
63
|
+
```css
|
|
64
|
+
--surface-context-success /* Success states/messages */
|
|
65
|
+
--surface-context-warning /* Warning states/messages */
|
|
66
|
+
--surface-context-danger /* Danger states/messages */
|
|
67
|
+
--surface-context-info /* Info states/messages */
|
|
68
|
+
--surface-context-signal /* Signal/notification */
|
|
69
|
+
--surface-context-dangeractive /* Active danger state */
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Input Surfaces** (Form elements)
|
|
73
|
+
```css
|
|
74
|
+
--surface-input-primary /* Primary input background */
|
|
75
|
+
--surface-input-secondary /* Secondary input background (disabled) */
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 2. Text Tokens (Typography Colors)
|
|
81
|
+
|
|
82
|
+
**Neutral Text** (General text)
|
|
83
|
+
```css
|
|
84
|
+
--text-neutral-default /* Primary body text */
|
|
85
|
+
--text-neutral-subdued /* Secondary/helper text */
|
|
86
|
+
--text-neutral-loud /* Emphasized text (headings) */
|
|
87
|
+
--text-neutral-disabled /* Disabled text */
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**State Text** (Interactive elements)
|
|
91
|
+
```css
|
|
92
|
+
--text-state-interactive /* Links, clickable text */
|
|
93
|
+
--text-state-selected /* Selected text/items */
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**On-Surface Text** (Text on colored backgrounds)
|
|
97
|
+
```css
|
|
98
|
+
--text-onsurface-onbrand /* Text on brand surfaces */
|
|
99
|
+
--text-onsurface-onaccent /* Text on accent surfaces */
|
|
100
|
+
--text-onsurface-oncontext /* Text on context surfaces */
|
|
101
|
+
--text-onsurface-oncontextactive /* Text on active context surfaces */
|
|
102
|
+
--text-onsurface-onsentiment /* Text on sentiment surfaces */
|
|
103
|
+
--text-onsurface-onhighlight /* Text on highlight surfaces */
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Context Text** (Semantic messages)
|
|
107
|
+
```css
|
|
108
|
+
--text-context-success /* Success message text */
|
|
109
|
+
--text-context-warning /* Warning message text */
|
|
110
|
+
--text-context-danger /* Danger/error message text */
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### 3. Border Tokens
|
|
116
|
+
|
|
117
|
+
**Neutral Borders** (General borders)
|
|
118
|
+
```css
|
|
119
|
+
--border-neutral-default /* Standard borders (inputs, cards) */
|
|
120
|
+
--border-neutral-subdued /* Subtle borders (dividers) */
|
|
121
|
+
--border-neutral-loud /* Emphasized borders */
|
|
122
|
+
--border-neutral-glassoutline /* Glass effect outlines */
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**State Borders** (Interactive states)
|
|
126
|
+
```css
|
|
127
|
+
--border-state-interactive /* Hover/interactive borders */
|
|
128
|
+
--border-state-selected /* Selected borders */
|
|
129
|
+
--border-state-focus /* Focus ring borders */
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Brand Borders**
|
|
133
|
+
```css
|
|
134
|
+
--border-brand-brand /* Brand-colored borders */
|
|
135
|
+
--border-brand-onbrand /* Borders on brand surfaces */
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Context Borders** (Semantic)
|
|
139
|
+
```css
|
|
140
|
+
--border-context-success /* Success borders */
|
|
141
|
+
--border-context-warning /* Warning borders */
|
|
142
|
+
--border-context-danger /* Danger borders */
|
|
143
|
+
--border-context-info /* Info borders */
|
|
144
|
+
--border-context-signal /* Signal borders */
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### 4. Icon Tokens
|
|
150
|
+
|
|
151
|
+
**Neutral Icons**
|
|
152
|
+
```css
|
|
153
|
+
--icon-neutral-default /* Primary icon color */
|
|
154
|
+
--icon-neutral-subdued /* Secondary icon color */
|
|
155
|
+
--icon-neutral-loud /* Emphasized icon color */
|
|
156
|
+
--icon-neutral-disabled /* Disabled icon color */
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**State Icons**
|
|
160
|
+
```css
|
|
161
|
+
--icon-state-interactive /* Interactive icon color (clickable) */
|
|
162
|
+
--icon-state-selected /* Selected icon color */
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**On-Surface Icons**
|
|
166
|
+
```css
|
|
167
|
+
--icon-onsurface-onbrand /* Icons on brand surfaces */
|
|
168
|
+
--icon-onsurface-onaccent /* Icons on accent surfaces */
|
|
169
|
+
--icon-onsurface-oncontext /* Icons on context surfaces */
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Context Icons**
|
|
173
|
+
```css
|
|
174
|
+
--icon-context-success /* Success icons */
|
|
175
|
+
--icon-context-warning /* Warning icons */
|
|
176
|
+
--icon-context-danger /* Danger icons */
|
|
177
|
+
--icon-context-info /* Info icons */
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### 5. Elevation Tokens (Shadows)
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
--elevation-subtle /* Level 1: Subtle shadows (hovercards) */
|
|
186
|
+
--elevation-moderate /* Level 2: Moderate shadows (dropdowns) */
|
|
187
|
+
--elevation-elevated /* Level 3: Elevated shadows (modals) */
|
|
188
|
+
--elevation-high /* Level 4: High shadows (dialogs) */
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Usage:**
|
|
192
|
+
```tsx
|
|
193
|
+
<div style={{ boxShadow: 'var(--elevation-moderate)' }}>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### 6. Border Radius Tokens
|
|
199
|
+
|
|
200
|
+
```css
|
|
201
|
+
--radius-sm /* 4px - Subtle rounding */
|
|
202
|
+
--radius-md /* 8px - Standard rounding (default) */
|
|
203
|
+
--radius-lg /* 12px - Large rounding (cards) */
|
|
204
|
+
--radius-full /* 9999px - Pills, avatars, circular */
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Usage:**
|
|
208
|
+
```tsx
|
|
209
|
+
<div style={{ borderRadius: 'var(--radius-md)' }}>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## State Completeness Checklist
|
|
215
|
+
|
|
216
|
+
For EVERY component, ensure ALL relevant states have tokens:
|
|
217
|
+
|
|
218
|
+
### Interactive Component States
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// Default state
|
|
222
|
+
color: var(--text-neutral-default)
|
|
223
|
+
|
|
224
|
+
// Hover state
|
|
225
|
+
color: var(--text-state-interactive)
|
|
226
|
+
|
|
227
|
+
// Focus state
|
|
228
|
+
outline: 2px solid var(--border-state-focus)
|
|
229
|
+
|
|
230
|
+
// Active state (being clicked)
|
|
231
|
+
background: var(--surface-brand-secondary)
|
|
232
|
+
|
|
233
|
+
// Disabled state
|
|
234
|
+
color: var(--text-neutral-disabled)
|
|
235
|
+
opacity: 0.6
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Form Input States
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// Default
|
|
242
|
+
background: var(--surface-input-primary)
|
|
243
|
+
border: 1px solid var(--border-neutral-default)
|
|
244
|
+
|
|
245
|
+
// Hover
|
|
246
|
+
border-color: var(--border-state-interactive)
|
|
247
|
+
|
|
248
|
+
// Focus
|
|
249
|
+
border-color: var(--border-state-focus)
|
|
250
|
+
outline: 2px solid var(--border-state-focus)
|
|
251
|
+
|
|
252
|
+
// Error
|
|
253
|
+
border-color: var(--border-context-danger)
|
|
254
|
+
|
|
255
|
+
// Disabled
|
|
256
|
+
background: var(--surface-input-secondary)
|
|
257
|
+
color: var(--text-neutral-disabled)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Message/Alert States
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// Success
|
|
264
|
+
background: var(--surface-context-success)
|
|
265
|
+
border: 1px solid var(--border-context-success)
|
|
266
|
+
color: var(--text-onsurface-oncontext)
|
|
267
|
+
|
|
268
|
+
// Warning
|
|
269
|
+
background: var(--surface-context-warning)
|
|
270
|
+
border: 1px solid var(--border-context-warning)
|
|
271
|
+
color: var(--text-onsurface-oncontext)
|
|
272
|
+
|
|
273
|
+
// Danger/Error
|
|
274
|
+
background: var(--surface-context-danger)
|
|
275
|
+
border: 1px solid var(--border-context-danger)
|
|
276
|
+
color: var(--text-onsurface-oncontext)
|
|
277
|
+
|
|
278
|
+
// Info
|
|
279
|
+
background: var(--surface-context-info)
|
|
280
|
+
border: 1px solid var(--border-context-info)
|
|
281
|
+
color: var(--text-onsurface-oncontext)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Paired Token Validation
|
|
287
|
+
|
|
288
|
+
**Critical Rule:** Text/icon tokens MUST match their surface.
|
|
289
|
+
|
|
290
|
+
### Pairing Rules
|
|
291
|
+
|
|
292
|
+
| Surface Token | Required Text Token | Required Icon Token |
|
|
293
|
+
|---------------|-------------------|-------------------|
|
|
294
|
+
| `--surface-brand-primary` | `--text-onsurface-onbrand` | `--icon-onsurface-onbrand` |
|
|
295
|
+
| `--surface-brand-accent` | `--text-onsurface-onaccent` | `--icon-onsurface-onaccent` |
|
|
296
|
+
| `--surface-context-*` | `--text-onsurface-oncontext` | `--icon-onsurface-oncontext` |
|
|
297
|
+
| `--surface-neutral-primary` | `--text-neutral-default` | `--icon-neutral-default` |
|
|
298
|
+
| `--surface-input-primary` | `--text-neutral-default` | `--icon-neutral-default` |
|
|
299
|
+
|
|
300
|
+
### Validation Examples
|
|
301
|
+
|
|
302
|
+
✅ **Correct:**
|
|
303
|
+
```tsx
|
|
304
|
+
<div style={{
|
|
305
|
+
background: 'var(--surface-brand-primary)',
|
|
306
|
+
color: 'var(--text-onsurface-onbrand)' /* Matches surface */
|
|
307
|
+
}}>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
❌ **Wrong:**
|
|
311
|
+
```tsx
|
|
312
|
+
<div style={{
|
|
313
|
+
background: 'var(--surface-brand-primary)',
|
|
314
|
+
color: 'var(--text-neutral-default)' /* MISMATCH - will have contrast issues */
|
|
315
|
+
}}>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
✅ **Correct:**
|
|
319
|
+
```tsx
|
|
320
|
+
<div style={{
|
|
321
|
+
background: 'var(--surface-context-danger)',
|
|
322
|
+
color: 'var(--text-onsurface-oncontext)',
|
|
323
|
+
borderColor: 'var(--border-context-danger)'
|
|
324
|
+
}}>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 4px Grid Spacing
|
|
330
|
+
|
|
331
|
+
**Never hardcode spacing** - use the 4px grid:
|
|
332
|
+
|
|
333
|
+
```css
|
|
334
|
+
/* Spacing scale (4px increments) */
|
|
335
|
+
0.25rem /* 4px */
|
|
336
|
+
0.5rem /* 8px */
|
|
337
|
+
0.75rem /* 12px */
|
|
338
|
+
1rem /* 16px */
|
|
339
|
+
1.25rem /* 20px */
|
|
340
|
+
1.5rem /* 24px */
|
|
341
|
+
2rem /* 32px */
|
|
342
|
+
2.5rem /* 40px */
|
|
343
|
+
3rem /* 48px */
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Examples:**
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
✅ padding: '1rem' /* 16px - on grid */
|
|
350
|
+
✅ margin: '0.5rem 1rem' /* 8px 16px - on grid */
|
|
351
|
+
✅ gap: '0.75rem' /* 12px - on grid */
|
|
352
|
+
|
|
353
|
+
❌ padding: '15px' /* Off-grid */
|
|
354
|
+
❌ margin: '17px' /* Off-grid */
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Common UI Patterns
|
|
360
|
+
|
|
361
|
+
### Pattern 1: Card
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
<div style={{
|
|
365
|
+
background: 'var(--surface-neutral-secondary)',
|
|
366
|
+
border: `1px solid var(--border-neutral-subdued)`,
|
|
367
|
+
borderRadius: 'var(--radius-lg)',
|
|
368
|
+
boxShadow: 'var(--elevation-subtle)',
|
|
369
|
+
padding: '1.5rem'
|
|
370
|
+
}}>
|
|
371
|
+
<h3 style={{ color: 'var(--text-neutral-loud)' }}>
|
|
372
|
+
Title
|
|
373
|
+
</h3>
|
|
374
|
+
<p style={{ color: 'var(--text-neutral-subdued)' }}>
|
|
375
|
+
Description
|
|
376
|
+
</p>
|
|
377
|
+
</div>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**State Tokens:**
|
|
381
|
+
- Default: `--surface-neutral-secondary`
|
|
382
|
+
- Hover: `boxShadow: var(--elevation-moderate)`
|
|
383
|
+
- Active/Selected: `border: 2px solid var(--border-state-selected)`
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
### Pattern 2: Button (Custom, if PrimeReact doesn't fit)
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
<button style={{
|
|
391
|
+
/* Default state */
|
|
392
|
+
background: 'var(--surface-brand-primary)',
|
|
393
|
+
color: 'var(--text-onsurface-onbrand)',
|
|
394
|
+
border: 'none',
|
|
395
|
+
borderRadius: 'var(--radius-md)',
|
|
396
|
+
padding: '0.75rem 1.5rem',
|
|
397
|
+
}}>
|
|
398
|
+
/* Hover state (CSS :hover) */
|
|
399
|
+
background: var(--surface-brand-secondary);
|
|
400
|
+
|
|
401
|
+
/* Focus state (CSS :focus) */
|
|
402
|
+
outline: 2px solid var(--border-state-focus);
|
|
403
|
+
outline-offset: 2px;
|
|
404
|
+
|
|
405
|
+
/* Disabled state (CSS :disabled) */
|
|
406
|
+
background: var(--surface-input-secondary);
|
|
407
|
+
color: var(--text-neutral-disabled);
|
|
408
|
+
cursor: not-allowed;
|
|
409
|
+
</button>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**But remember:** Use PrimeReact `<Button>` instead whenever possible!
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### Pattern 3: Alert/Message
|
|
417
|
+
|
|
418
|
+
```tsx
|
|
419
|
+
/* Success */
|
|
420
|
+
<div style={{
|
|
421
|
+
background: 'var(--surface-context-success)',
|
|
422
|
+
border: `1px solid var(--border-context-success)`,
|
|
423
|
+
borderRadius: 'var(--radius-md)',
|
|
424
|
+
padding: '1rem',
|
|
425
|
+
color: 'var(--text-onsurface-oncontext)'
|
|
426
|
+
}}>
|
|
427
|
+
<i className="pi pi-check" style={{
|
|
428
|
+
color: 'var(--icon-onsurface-oncontext)'
|
|
429
|
+
}} />
|
|
430
|
+
Success message
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
/* Error */
|
|
434
|
+
<div style={{
|
|
435
|
+
background: 'var(--surface-context-danger)',
|
|
436
|
+
border: `1px solid var(--border-context-danger)`,
|
|
437
|
+
borderRadius: 'var(--radius-md)',
|
|
438
|
+
padding: '1rem',
|
|
439
|
+
color: 'var(--text-onsurface-oncontext)'
|
|
440
|
+
}}>
|
|
441
|
+
<i className="pi pi-times" style={{
|
|
442
|
+
color: 'var(--icon-onsurface-oncontext)'
|
|
443
|
+
}} />
|
|
444
|
+
Error message
|
|
445
|
+
</div>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Or use PrimeReact Message component:**
|
|
449
|
+
```tsx
|
|
450
|
+
<Message severity="success" text="Success message" />
|
|
451
|
+
<Message severity="error" text="Error message" />
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### Pattern 4: Input with States
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
function StyledInput({ error, disabled }) {
|
|
460
|
+
return (
|
|
461
|
+
<input
|
|
462
|
+
disabled={disabled}
|
|
463
|
+
style={{
|
|
464
|
+
background: disabled
|
|
465
|
+
? 'var(--surface-input-secondary)'
|
|
466
|
+
: 'var(--surface-input-primary)',
|
|
467
|
+
border: `1px solid ${
|
|
468
|
+
error
|
|
469
|
+
? 'var(--border-context-danger)'
|
|
470
|
+
: 'var(--border-neutral-default)'
|
|
471
|
+
}`,
|
|
472
|
+
borderRadius: 'var(--radius-md)',
|
|
473
|
+
padding: '0.75rem',
|
|
474
|
+
color: disabled
|
|
475
|
+
? 'var(--text-neutral-disabled)'
|
|
476
|
+
: 'var(--text-neutral-default)',
|
|
477
|
+
}}
|
|
478
|
+
/* Focus state in CSS */
|
|
479
|
+
onFocus={(e) => {
|
|
480
|
+
e.target.style.borderColor = 'var(--border-state-focus)'
|
|
481
|
+
e.target.style.outline = '2px solid var(--border-state-focus)'
|
|
482
|
+
}}
|
|
483
|
+
/>
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**But use PrimeReact InputText instead:**
|
|
489
|
+
```tsx
|
|
490
|
+
<InputText
|
|
491
|
+
disabled={disabled}
|
|
492
|
+
className={error ? 'p-invalid' : ''}
|
|
493
|
+
/>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Token Selection Decision Tree
|
|
499
|
+
|
|
500
|
+
### Step 1: Identify Element Type
|
|
501
|
+
|
|
502
|
+
- **Background** → Surface tokens
|
|
503
|
+
- **Text** → Text tokens
|
|
504
|
+
- **Border/Outline** → Border tokens
|
|
505
|
+
- **Icon** → Icon tokens
|
|
506
|
+
- **Shadow** → Elevation tokens
|
|
507
|
+
- **Spacing** → 4px grid (rem values)
|
|
508
|
+
|
|
509
|
+
### Step 2: Identify Semantic Category
|
|
510
|
+
|
|
511
|
+
- **Neutral** (general UI) → neutral tokens
|
|
512
|
+
- **Brand** (identity) → brand tokens
|
|
513
|
+
- **Interactive** (clickable) → state tokens
|
|
514
|
+
- **Success/Warning/Danger/Info** → context tokens
|
|
515
|
+
- **On colored surface** → onsurface tokens
|
|
516
|
+
|
|
517
|
+
### Step 3: Identify State
|
|
518
|
+
|
|
519
|
+
- **Default** → base token
|
|
520
|
+
- **Hover** → interactive token
|
|
521
|
+
- **Focus** → focus token
|
|
522
|
+
- **Active** → secondary/active token
|
|
523
|
+
- **Disabled** → disabled token
|
|
524
|
+
- **Selected** → selected token
|
|
525
|
+
|
|
526
|
+
### Step 4: Verify Pairing
|
|
527
|
+
|
|
528
|
+
- If using surface token → ensure matching text/icon token
|
|
529
|
+
- If using context surface → use `--text-onsurface-oncontext`
|
|
530
|
+
- If using brand surface → use `--text-onsurface-onbrand`
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Anti-Patterns
|
|
535
|
+
|
|
536
|
+
### ❌ Hardcoded Colors
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
❌ color: '#333333'
|
|
540
|
+
❌ background: 'rgb(59, 130, 246)'
|
|
541
|
+
❌ border: '1px solid #e5e7eb'
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
✅ **Correct:**
|
|
545
|
+
```tsx
|
|
546
|
+
✅ color: 'var(--text-neutral-default)'
|
|
547
|
+
✅ background: 'var(--surface-brand-primary)'
|
|
548
|
+
✅ border: `1px solid var(--border-neutral-default)`
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
### ❌ Using Foundation/Primitive Tokens
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
❌ color: 'var(--foundation-sky-700)'
|
|
557
|
+
❌ background: 'var(--foundation-rock-100)'
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
✅ **Correct:**
|
|
561
|
+
```tsx
|
|
562
|
+
✅ color: 'var(--text-neutral-default)'
|
|
563
|
+
✅ background: 'var(--surface-neutral-secondary)'
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Why:** Foundation tokens don't adapt to theme changes. Semantic tokens do.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
### ❌ Mismatched Paired Tokens
|
|
571
|
+
|
|
572
|
+
```tsx
|
|
573
|
+
❌ <div style={{
|
|
574
|
+
background: 'var(--surface-brand-primary)',
|
|
575
|
+
color: 'var(--text-neutral-default)' /* Wrong pairing */
|
|
576
|
+
}}>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
✅ **Correct:**
|
|
580
|
+
```tsx
|
|
581
|
+
✅ <div style={{
|
|
582
|
+
background: 'var(--surface-brand-primary)',
|
|
583
|
+
color: 'var(--text-onsurface-onbrand)' /* Correct pairing */
|
|
584
|
+
}}>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
### ❌ Incomplete State Handling
|
|
590
|
+
|
|
591
|
+
```tsx
|
|
592
|
+
❌ /* Only default state */
|
|
593
|
+
<button style={{ color: 'var(--text-neutral-default)' }}>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
✅ **Correct:**
|
|
597
|
+
```tsx
|
|
598
|
+
✅ <button
|
|
599
|
+
style={{
|
|
600
|
+
color: isDisabled
|
|
601
|
+
? 'var(--text-neutral-disabled)'
|
|
602
|
+
: 'var(--text-neutral-default)'
|
|
603
|
+
}}
|
|
604
|
+
onMouseEnter={(e) => {
|
|
605
|
+
if (!isDisabled) {
|
|
606
|
+
e.target.style.color = 'var(--text-state-interactive)'
|
|
607
|
+
}
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Or better - use PrimeReact Button which handles all states.**
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Creating New Semantic Tokens
|
|
617
|
+
|
|
618
|
+
**When you MUST create a new token** (rare), follow this template:
|
|
619
|
+
|
|
620
|
+
### Naming Convention
|
|
621
|
+
|
|
622
|
+
```
|
|
623
|
+
--[category]-[semantic-group]-[variant]
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Examples:**
|
|
627
|
+
```css
|
|
628
|
+
--surface-sentiment-positive /* New sentiment surface */
|
|
629
|
+
--text-role-admin /* New role-based text */
|
|
630
|
+
--border-state-visited /* New state border */
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Token Definition Template
|
|
634
|
+
|
|
635
|
+
**In `semantic-light.css`:**
|
|
636
|
+
```css
|
|
637
|
+
/* Add new token */
|
|
638
|
+
--surface-sentiment-positive: var(--foundation-forest-100);
|
|
639
|
+
--text-onsurface-onsentiment: var(--foundation-forest-900);
|
|
640
|
+
--border-sentiment-positive: var(--foundation-forest-500);
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**In `semantic-dark.css`:**
|
|
644
|
+
```css
|
|
645
|
+
/* Add dark mode variant */
|
|
646
|
+
--surface-sentiment-positive: var(--foundation-forest-800);
|
|
647
|
+
--text-onsurface-onsentiment: var(--foundation-forest-050);
|
|
648
|
+
--border-sentiment-positive: var(--foundation-forest-400);
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Validation Checklist
|
|
652
|
+
|
|
653
|
+
- [ ] Token name follows convention
|
|
654
|
+
- [ ] Defined in both light AND dark themes
|
|
655
|
+
- [ ] Paired tokens created (surface + text + border + icon)
|
|
656
|
+
- [ ] Contrast validated (APCA)
|
|
657
|
+
- [ ] Documented in this guide
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## Token Bundle Template
|
|
662
|
+
|
|
663
|
+
When providing tokens for a component, use this format:
|
|
664
|
+
|
|
665
|
+
```
|
|
666
|
+
Component: [Component name]
|
|
667
|
+
Intent: [Semantic purpose]
|
|
668
|
+
|
|
669
|
+
Token Bundle:
|
|
670
|
+
|
|
671
|
+
Default State:
|
|
672
|
+
- surface: var(--surface-neutral-secondary)
|
|
673
|
+
- text: var(--text-neutral-default)
|
|
674
|
+
- border: var(--border-neutral-default)
|
|
675
|
+
- icon: var(--icon-neutral-default)
|
|
676
|
+
- elevation: var(--elevation-subtle)
|
|
677
|
+
|
|
678
|
+
Hover State:
|
|
679
|
+
- surface: (unchanged)
|
|
680
|
+
- border: var(--border-state-interactive)
|
|
681
|
+
- elevation: var(--elevation-moderate)
|
|
682
|
+
|
|
683
|
+
Focus State:
|
|
684
|
+
- outline: 2px solid var(--border-state-focus)
|
|
685
|
+
- outline-offset: 2px
|
|
686
|
+
|
|
687
|
+
Active State:
|
|
688
|
+
- surface: var(--surface-neutral-tertiary)
|
|
689
|
+
|
|
690
|
+
Disabled State:
|
|
691
|
+
- surface: var(--surface-input-secondary)
|
|
692
|
+
- text: var(--text-neutral-disabled)
|
|
693
|
+
- icon: var(--icon-neutral-disabled)
|
|
694
|
+
- opacity: 0.6
|
|
695
|
+
|
|
696
|
+
Spacing:
|
|
697
|
+
- padding: 1rem (16px)
|
|
698
|
+
- gap: 0.75rem (12px)
|
|
699
|
+
- border-radius: var(--radius-md)
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## Quick Reference
|
|
705
|
+
|
|
706
|
+
### Most Common Tokens
|
|
707
|
+
|
|
708
|
+
**Default text:** `--text-neutral-default`
|
|
709
|
+
**Subdued text:** `--text-neutral-subdued`
|
|
710
|
+
**Link text:** `--text-state-interactive`
|
|
711
|
+
|
|
712
|
+
**Default background:** `--surface-neutral-primary`
|
|
713
|
+
**Card background:** `--surface-neutral-secondary`
|
|
714
|
+
**Brand background:** `--surface-brand-primary`
|
|
715
|
+
|
|
716
|
+
**Default border:** `--border-neutral-default`
|
|
717
|
+
**Focus border:** `--border-state-focus`
|
|
718
|
+
|
|
719
|
+
**Default icon:** `--icon-neutral-default`
|
|
720
|
+
|
|
721
|
+
**Card shadow:** `--elevation-subtle`
|
|
722
|
+
**Modal shadow:** `--elevation-elevated`
|
|
723
|
+
|
|
724
|
+
**Default radius:** `--radius-md`
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## Summary
|
|
729
|
+
|
|
730
|
+
**Your job as Semantic Token Intent Agent:**
|
|
731
|
+
|
|
732
|
+
1. **Translate design intent** → semantic token categories
|
|
733
|
+
2. **Ensure state completeness** → all 5 states covered
|
|
734
|
+
3. **Validate token pairing** → text matches surface
|
|
735
|
+
4. **Enforce 4px grid** → all spacing on-grid
|
|
736
|
+
5. **Prevent hardcoded values** → only semantic tokens
|
|
737
|
+
6. **Guide token creation** → when absolutely necessary
|
|
738
|
+
|
|
739
|
+
**Remember:** Semantic tokens aren't just "color variables" - they're a design language that ensures consistency, accessibility, and theme-ability.
|