Token pipeline (Style Dictionary v4, DTCG format): - Primitive tokens: colour palettes (brand, sage, neutral, feedback), typography (3 font families, 21-variant type scale), spacing (4px grid), border radius, shadows, opacity - Semantic tokens: text, surface, border, interactive, feedback colours; typography roles; layout spacing - Component tokens: Button (4 sizes), Input (2 sizes) - Generated outputs: CSS custom properties, JS ES6 module, flat JSON Atoms (3 components): - Button: contained/soft/outlined/text × primary/secondary, 4 sizes, loading state, underline for text variant - Typography: 21 variants across display/heading/body/label/caption/overline, maxLines truncation - Input: external label, helper text, error/success validation, start/end icons, required indicator, 2 sizes, multiline support Infrastructure: - MUI v5 theme with full token mapping - Storybook 8 with autodocs - Claude Code agents and skills for token/component workflows - Design system documentation and cross-session memory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
145 lines
5.6 KiB
Markdown
145 lines
5.6 KiB
Markdown
# Token conventions
|
|
|
|
These conventions MUST be followed by any agent creating or modifying tokens.
|
|
|
|
## W3C DTCG format
|
|
|
|
All tokens use the DTCG JSON format. Every token has `$value`, `$type`, and `$description`.
|
|
|
|
```json
|
|
{
|
|
"color": {
|
|
"brand": {
|
|
"primary": {
|
|
"$value": "#1B4965",
|
|
"$type": "color",
|
|
"$description": "Primary brand colour — deep navy. Used for primary actions, key headings, and trust-building UI elements."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Naming rules
|
|
|
|
### General
|
|
- Use dot notation for hierarchy in documentation: `color.brand.primary`
|
|
- In JSON, dots become nested objects
|
|
- Names are lowercase, no spaces, no special characters except hyphens for compound words
|
|
- Names must be descriptive of purpose (semantic) or scale position (primitive)
|
|
|
|
### Primitive naming
|
|
Primitives describe WHAT the value is, not WHERE it's used.
|
|
|
|
```
|
|
color.{hue}.{scale} → color.blue.500, color.neutral.100
|
|
spacing.{scale} → spacing.1, spacing.2, spacing.4, spacing.8
|
|
fontSize.{scale} → fontSize.xs, fontSize.sm, fontSize.base, fontSize.lg
|
|
fontWeight.{name} → fontWeight.regular, fontWeight.medium, fontWeight.bold
|
|
fontFamily.{purpose} → fontFamily.heading, fontFamily.body, fontFamily.mono
|
|
borderRadius.{scale} → borderRadius.sm, borderRadius.md, borderRadius.lg, borderRadius.full
|
|
shadow.{scale} → shadow.sm, shadow.md, shadow.lg
|
|
lineHeight.{scale} → lineHeight.tight, lineHeight.normal, lineHeight.relaxed
|
|
letterSpacing.{scale} → letterSpacing.tight, letterSpacing.normal, letterSpacing.wide
|
|
opacity.{scale} → opacity.disabled, opacity.hover, opacity.overlay
|
|
```
|
|
|
|
### Colour scale convention
|
|
Use a 50-950 scale (matching Tailwind/MUI convention):
|
|
- 50: Lightest tint (backgrounds, subtle fills)
|
|
- 100-200: Light tints (hover states, borders)
|
|
- 300-400: Mid tones (secondary text, icons)
|
|
- 500: Base/reference value
|
|
- 600-700: Strong tones (primary text on light bg, active states)
|
|
- 800-900: Darkest shades (headings, high-contrast text)
|
|
- 950: Near-black (used sparingly)
|
|
|
|
### Semantic naming
|
|
Semantic tokens describe WHERE and WHY a value is used.
|
|
|
|
```
|
|
color.text.{variant} → color.text.primary, color.text.secondary, color.text.disabled, color.text.inverse
|
|
color.surface.{variant} → color.surface.default, color.surface.raised, color.surface.overlay
|
|
color.border.{variant} → color.border.default, color.border.strong, color.border.subtle
|
|
color.interactive.{state} → color.interactive.default, color.interactive.hover, color.interactive.active, color.interactive.disabled
|
|
color.feedback.{type} → color.feedback.success, color.feedback.warning, color.feedback.error, color.feedback.info
|
|
spacing.component.{size} → spacing.component.xs, spacing.component.sm, spacing.component.md, spacing.component.lg
|
|
spacing.layout.{size} → spacing.layout.section, spacing.layout.page, spacing.layout.gutter
|
|
typography.{role} → typography.display, typography.heading, typography.body, typography.caption, typography.label
|
|
```
|
|
|
|
### Component token naming
|
|
Component tokens are scoped to a specific component.
|
|
|
|
```
|
|
{component}.{property}.{state}
|
|
button.background.default
|
|
button.background.hover
|
|
button.background.active
|
|
button.background.disabled
|
|
button.text.default
|
|
button.text.disabled
|
|
button.border.default
|
|
button.border.focus
|
|
button.padding.horizontal
|
|
button.padding.vertical
|
|
button.borderRadius
|
|
card.background.default
|
|
card.border.default
|
|
card.padding
|
|
card.borderRadius
|
|
card.shadow
|
|
input.background.default
|
|
input.background.focus
|
|
input.border.default
|
|
input.border.error
|
|
input.border.focus
|
|
input.text.default
|
|
input.text.placeholder
|
|
```
|
|
|
|
## Alias rules
|
|
|
|
- Semantic tokens MUST reference primitives (never hardcode values)
|
|
- Component tokens MUST reference semantic tokens (never reference primitives directly)
|
|
- This creates the chain: component → semantic → primitive
|
|
- Exception: spacing and borderRadius component tokens may reference primitives directly when the semantic layer adds no value
|
|
|
|
```json
|
|
// CORRECT: component → semantic → primitive
|
|
"button.background.default": { "$value": "{color.interactive.default}" }
|
|
"color.interactive.default": { "$value": "{color.brand.primary}" }
|
|
"color.brand.primary": { "$value": "{color.blue.700}" }
|
|
"color.blue.700": { "$value": "#1B4965" }
|
|
|
|
// WRONG: component referencing a primitive directly
|
|
"button.background.default": { "$value": "{color.blue.700}" }
|
|
```
|
|
|
|
## Accessibility requirements
|
|
|
|
- All colour combinations used for text must meet WCAG 2.1 AA contrast ratio (4.5:1 for normal text, 3:1 for large text)
|
|
- Interactive elements must have a visible focus indicator
|
|
- Disabled states must still be distinguishable from enabled states
|
|
- When creating colour tokens, note the contrast ratio with common background colours in the `$description`
|
|
|
|
## File organisation
|
|
|
|
```
|
|
tokens/
|
|
├── primitives/
|
|
│ ├── colours.json # All colour primitives (brand, neutral, feedback hues)
|
|
│ ├── typography.json # Font families, sizes, weights, line heights
|
|
│ ├── spacing.json # Spacing scale, border radius, sizing
|
|
│ └── effects.json # Shadows, opacity values
|
|
├── semantic/
|
|
│ ├── colours.json # Semantic colour mappings
|
|
│ ├── typography.json # Typography role mappings
|
|
│ └── spacing.json # Layout and component spacing
|
|
└── component/
|
|
├── button.json # Button-specific tokens
|
|
├── input.json # Input-specific tokens
|
|
├── card.json # Card-specific tokens
|
|
└── ... # One file per component that needs specific tokens
|
|
```
|