_categorical.scss as CSS custom properties (
+ --color-tag-1 through --color-tag-20).
+ Currently in constants.ts pending migration. These are
+ NOT semantic tokens — they are categorical identifiers that need
+ to be visually distinct from each other.
+ >
+ }
+ >
+
+ Default tag colour: {DEFAULT_TAG_COLOUR}
+
_categorical.scss as --color-project-1{' '}
+ through --color-project-6. Currently in{' '}
+ constants.ts pending migration. Decorative — not
+ tied to any UI role or theme.
+ >
+ }
+ >
+ common/constants.ts as{' '}
+ Constants.featureHealth. These should migrate to semantic
+ feedback tokens: var(--color-success-default) and{' '}
+ var(--color-warning-default).
+ >
+ }
+ >
+ var(--color-success-default)
+ var(--color-warning-default)
+ web/styles/_primitives.scss. Add a
+ new variable to the SCSS file and it will appear here automatically.
+ >
+ }
+ >
+ {scales.map((scale) => (
+ | Category | Token prefix | Use for | Examples |
|---|---|---|---|
| Surface | {'--color-surface-*'} | Backgrounds — page, card, panel, input, hover. Includes action (brand purple) and feedback surfaces. | Page bg, card bg, button bg (surface-action), banner bg (surface-danger, surface-success). |
| Text | {'--color-text-*'} | Body text, headings, muted/subtle text. Includes feedback text colours. | Body copy, headings, error text (text-danger), success text (text-success). |
| Border | {'--color-border-*'} | Dividers, input borders, card outlines. Includes feedback borders. | Input borders, card outlines, error borders (border-danger). |
| Icon | {'--color-icon-*'} | Icon fills — default, secondary, disabled, and feedback variants. | Navigation icons, status icons (icon-danger, icon-success). |
| Part | Options | Example |
|---|---|---|
category | surface, text, border, icon | --color-text- |
variant | default, secondary, disabled, danger, success, warning, info. Surface also has: subtle, muted, emphasis, hover, active. Text also has: tertiary, on-fill. | --color-text-danger |
| Variant | Light mode | Dark mode | Why |
|---|---|---|---|
default | 500–600 | 400–500 | Needs to read well on light/dark surfaces |
hover | 600–700 (darker) | 400–600 (lighter) | Light: darken on hover. Dark: lighten on hover. |
active | 700–800 (darkest) | 300–400 (lightest) | More intense than hover in both modes |
subtle | 8% opacity of default | Opaque dark tint | Light: transparent tint. Dark: opaque to avoid stacking issues. |
_tokens.scss with .dark overrides.
+ >
+ }
+ >
+ {token}
+
+ {value}
+
+
+ {description}
+
+
+ In dark mode, shadows are harder to perceive. The dark overrides use
+ stronger opacity values (0.20–0.40 vs 0.05–0.20). Additionally, higher
+ elevation surfaces should use progressively lighter background colours
+ (e.g. --color-surface-emphasis for modals) to maintain
+ visual hierarchy without relying solely on shadows.
+
| Path | +Purpose | +
|---|---|
| documentation/ | +Storybook stories and documentation (self-contained, portable) | +
| web/components/ | +React components | +
| web/styles/ | +SCSS styles and design tokens | +
| common/ | +Shared state, services, types, and utilities | +
_tokens.scss. Pair a duration with an easing:{' '}
+
+ {'transition: all var(--duration-normal) var(--easing-standard)'}
+
+ >
+ }
+ >
+ | Token | +Value | +Usage | +
|---|---|---|
+ {token}
+ |
+
+ {value}
+ |
+ + {description} + | +
| Token | +Value | +Usage | +
|---|---|---|
+ {token}
+ |
+
+ {value}
+ |
+ + {description} + | +
+ Click each box to see the duration + easing combination in action. +
+_tokens.scss. Use{' '}
+ {'border-radius: var(--radius-md)'} in SCSS or{' '}
+ {'radius.md'} from common/theme/tokens in
+ TypeScript.
+ >
+ }
+ >
+ | Preview | +Token | +Value | +Usage | +
|---|---|---|---|
| + + | +
+ {token}
+ |
+
+ {value}
+ |
+ + {description} + | +
common/theme/tokens.json. Toggle the theme in the toolbar
+ to see computed values update.
+ >
+ }
+ >
+ {groups.map((group) => (
+ _tokens.scss as CSS custom
+ properties. Use var(--space-4) in SCSS or{' '}
+ space[4] from common/theme/tokens in
+ TypeScript. Names follow the Tailwind convention.
+ >
+ }
+ >
+ | Preview | +Token | +Value | +Usage | +
|---|---|---|---|
| + + | +
+ {token}
+ |
+
+ {value}
+ |
+ + {description} + | +
--space-1 (4px) or{' '}
+ --space-2 (8px)
+ --space-3{' '}
+ (12px) to --space-4 (16px)
+ --space-4 (16px) to{' '}
+ --space-6 (24px)
+ --space-8 (32px) and
+ above
+ | File | Purpose | When to edit |
|---|---|---|
common/theme/tokens.json | Single source of truth. All token values and descriptions. | Every token change starts here. |
common/theme/tokens.ts | Generated. TypeScript exports with var(--token, fallback) pattern. | Never — auto-generated. |
web/styles/_tokens.scss | Generated. CSS custom properties with :root and .dark. | Never — auto-generated. |
documentation/TokenReference.generated.stories.tsx | Generated. Flat JSX for Storybook MCP. | Never — auto-generated. |
web/styles/_primitives.scss | Raw colour scales (50-950). | Only when adding a new colour family or adjusting a scale. |
| Step | What happens | Triggered by |
|---|---|---|
| 1 | scripts/generate-tokens.mjs reads tokens.json | Pre-commit hook (lint-staged) when tokens.json is staged |
| 2 | Generates tokens.ts, _tokens.scss, and TokenReference.generated.stories.tsx | Automatic |
| 3 | Generated files are auto-staged and included in the commit | Automatic |
| 4 | Chromatic deploys the updated Storybook. Storybook MCP serves the flat JSX — AI agents can read all token data. | CI (on PR) |
| Story | Type | Purpose |
|---|---|---|
| Token Reference | Auto-generated (codegen) | MCP-optimised. Every token inlined as flat JSX. AI agents read this. |
| Semantic Colour Tokens | Auto-generated (runtime) | Visual. Reads --color-* CSS vars from the DOM. Shows computed colours with theme toggle. |
| Colour Palette | Auto-generated (build time) | Visual. Parses _primitives.scss and renders swatch grids. |
| Spacing, Radius, Elevation, Motion | Imports from tokens.ts | Visual. Shows previews (bars, rounded boxes, shadow cards, interactive demos). Data derived from tokens.ts. |
| Token | +Value | +
|---|---|
+ --color-surface-default
+ |
+
+ #ffffff
+ |
+
+ --color-surface-subtle
+ |
+
+ #fafafb
+ |
+
+ --color-surface-muted
+ |
+
+ #eff1f4
+ |
+
+ --color-surface-emphasis
+ |
+
+ #e0e3e9
+ |
+
+ --color-surface-hover
+ |
+
+ rgba(0, 0, 0, 0.08)
+ |
+
+ --color-surface-active
+ |
+
+ rgba(0, 0, 0, 0.16)
+ |
+
+ --color-surface-action
+ |
+
+ #6837fc
+ |
+
+ --color-surface-action-hover
+ |
+
+ #4e25db
+ |
+
+ --color-surface-action-active
+ |
+
+ #3919b7
+ |
+
+ --color-surface-action-subtle
+ |
+
+ rgba(104, 55, 252, 0.08)
+ |
+
+ --color-surface-action-muted
+ |
+
+ rgba(104, 55, 252, 0.16)
+ |
+
+ --color-surface-danger
+ |
+
+ rgba(239, 77, 86, 0.08)
+ |
+
+ --color-surface-success
+ |
+
+ rgba(39, 171, 149, 0.08)
+ |
+
+ --color-surface-warning
+ |
+
+ rgba(255, 159, 67, 0.08)
+ |
+
+ --color-surface-info
+ |
+
+ rgba(10, 173, 223, 0.08)
+ |
+
| Token | +Value | +
|---|---|
+ --color-text-default
+ |
+
+ #1a2634
+ |
+
+ --color-text-secondary
+ |
+
+ #656d7b
+ |
+
+ --color-text-tertiary
+ |
+
+ #9da4ae
+ |
+
+ --color-text-disabled
+ |
+
+ #9da4ae
+ |
+
+ --color-text-on-fill
+ |
+
+ #ffffff
+ |
+
+ --color-text-action
+ |
+
+ #6837fc
+ |
+
+ --color-text-danger
+ |
+
+ #ef4d56
+ |
+
+ --color-text-success
+ |
+
+ #27ab95
+ |
+
+ --color-text-warning
+ |
+
+ #ff9f43
+ |
+
+ --color-text-info
+ |
+
+ #0aaddf
+ |
+
| Token | +Value | +
|---|---|
+ --color-border-default
+ |
+
+ rgba(101, 109, 123, 0.16)
+ |
+
+ --color-border-strong
+ |
+
+ rgba(101, 109, 123, 0.24)
+ |
+
+ --color-border-disabled
+ |
+
+ rgba(101, 109, 123, 0.08)
+ |
+
+ --color-border-action
+ |
+
+ #6837fc
+ |
+
+ --color-border-danger
+ |
+
+ #ef4d56
+ |
+
+ --color-border-success
+ |
+
+ #27ab95
+ |
+
+ --color-border-warning
+ |
+
+ #ff9f43
+ |
+
+ --color-border-info
+ |
+
+ #0aaddf
+ |
+
| Token | +Value | +
|---|---|
+ --color-icon-default
+ |
+
+ #1a2634
+ |
+
+ --color-icon-secondary
+ |
+
+ #656d7b
+ |
+
+ --color-icon-disabled
+ |
+
+ #9da4ae
+ |
+
+ --color-icon-action
+ |
+
+ #6837fc
+ |
+
+ --color-icon-danger
+ |
+
+ #ef4d56
+ |
+
+ --color-icon-success
+ |
+
+ #27ab95
+ |
+
+ --color-icon-warning
+ |
+
+ #ff9f43
+ |
+
+ --color-icon-info
+ |
+
+ #0aaddf
+ |
+
| Token | +Value | +Usage | +
|---|---|---|
+ --space-0
+ |
+
+ 0px
+ |
+ No spacing. Use for flush edges between elements. | +
+ --space-1
+ |
+
+ 4px
+ |
+ Tight inline spacing. Icon-to-text gap, badge padding. | +
+ --space-2
+ |
+
+ 8px
+ |
+ + Default inline gap. Space between icon and label, between chips. + | +
+ --space-3
+ |
+
+ 12px
+ |
+ + Comfortable inner padding. Input padding, small card padding. + | +
+ --space-4
+ |
+
+ 16px
+ |
+ + Standard component padding. Card padding, section gap, button + horizontal padding. + | +
+ --space-5
+ |
+
+ 20px
+ |
+ Button horizontal padding (large). Modal body padding. | +
+ --space-6
+ |
+
+ 24px
+ |
+ + Section spacing. Gap between card groups, modal header/body + padding. + | +
+ --space-8
+ |
+
+ 32px
+ |
+ Large section spacing. Page section margins. | +
+ --space-10
+ |
+
+ 40px
+ |
+ Extra-large spacing. Major page sections. | +
+ --space-12
+ |
+
+ 48px
+ |
+ Page-level vertical spacing between major sections. | +
+ --space-16
+ |
+
+ 64px
+ |
+ Maximum spacing. Hero sections, page-level separation. | +
+ --space-0_5
+ |
+
+ 2px
+ |
+ + Fine optical adjustment only. Never use for component padding. + | +
| Token | +Value | +Usage | +
|---|---|---|
+ --radius-none
+ |
+
+ 0px
+ |
+ Sharp corners. Tables, dividers. | +
+ --radius-xs
+ |
+
+ 2px
+ |
+ Barely rounded. Badges, tags. | +
+ --radius-sm
+ |
+
+ 4px
+ |
+ Buttons, inputs, small interactive elements. | +
+ --radius-md
+ |
+
+ 6px
+ |
+ Default component radius. Cards, dropdowns, tooltips. | +
+ --radius-lg
+ |
+
+ 8px
+ |
+ Large cards, panels. | +
+ --radius-xl
+ |
+
+ 10px
+ |
+ Extra-large containers. | +
+ --radius-2xl
+ |
+
+ 18px
+ |
+ Modals. | +
+ --radius-full
+ |
+
+ 9999px
+ |
+ Pill shapes, avatars, circular elements. | +
| Token | +Value | +Usage | +
|---|---|---|
+ --shadow-sm
+ |
+
+ 0px 1px 2px rgba(0, 0, 0, 0.05)
+ |
+ Subtle lift. Buttons on hover, input focus ring companion. | +
+ --shadow-md
+ |
+
+ 0px 4px 8px rgba(0, 0, 0, 0.12)
+ |
+ + Cards, dropdowns, popovers. Default elevation for floating + elements. + | +
+ --shadow-lg
+ |
+
+ 0px 8px 16px rgba(0, 0, 0, 0.15)
+ |
+ + Modals, drawers, slide-over panels. High elevation for overlay + content. + | +
+ --shadow-xl
+ |
+
+ 0px 12px 24px rgba(0, 0, 0, 0.20)
+ |
+ + Toast notifications, command palettes. Maximum elevation for + urgent content. + | +
| Token | +Value | +Usage | +
|---|---|---|
+ --duration-fast
+ |
+
+ 100ms
+ |
+ + Quick feedback. Hover states, toggle switches, checkbox ticks. + | +
+ --duration-normal
+ |
+
+ 200ms
+ |
+ + Standard transitions. Dropdown open, tooltip appear, tab switch. + | +
+ --duration-slow
+ |
+
+ 300ms
+ |
+ + Deliberate emphasis. Modal enter, drawer slide, accordion expand. + | +
| Token | +Value | +Usage | +
|---|---|---|
+ --easing-standard
+ |
+
+ cubic-bezier(0.2, 0, 0.38, 0.9)
+ |
+ + Default for most transitions. Smooth deceleration. Use for + elements moving within the page. + | +
+ --easing-entrance
+ |
+
+ cubic-bezier(0.0, 0, 0.38, 0.9)
+ |
+ + Elements entering the viewport. Decelerates into resting position. + Modals, toasts, slide-ins. + | +
+ --easing-exit
+ |
+
+ cubic-bezier(0.2, 0, 1, 0.9)
+ |
+ + Elements leaving the viewport. Accelerates out of view. Closing + modals, dismissing toasts. + | +
+ Dark mode overrides use stronger opacity (0.20-0.40 vs 0.05-0.20). + Higher elevation surfaces should use lighter backgrounds + (--color-surface-emphasis) rather than relying solely on shadows. +
+ ++ Combine a duration with an easing: transition: all + var(--duration-normal) var(--easing-standard). Use --easing-entrance for + elements appearing, --easing-exit for elements leaving. +
+| Token | Size | Line height | Weight |
|---|---|---|---|
--font-h1-size | 42px | 46px | 700 |
--font-h2-size | 34px | 40px | 700 |
--font-h3-size | 30px | 40px | 600 |
--font-h4-size | 24px | 32px | 600 |
--font-h5-size | 18px | 28px | 600 |
--font-h6-size | 16px | 24px | 600 |
| Token | Size | Line height | Weight | Usage |
|---|---|---|---|---|
--font-body-size | 14px | 20px | 400 | Default body text |
--font-body-sm-size | 13px | 18px | 400 | Table headers, subtitles |
--font-caption-size | 12px | 16px | 400 | Helper text, small labels |
--font-caption-xs-size | 11px | 14px | 400 | Badges, minimal text |
--font-label-size | 12px | 16px | 600 | Form labels, section labels |
| Token | Value | Usage |
|---|---|---|
--font-weight-regular | 400 | Body text, form inputs |
--font-weight-medium | 500 | Buttons, inputs, alerts, tabs |
--font-weight-semibold | 600 | Active tab states, sidebar links |
--font-weight-bold | 700 | Headings, unread badges |
| Token | Value |
|---|---|
--font-family | 'OpenSans', sans-serif |
{description}
+ {children} +{name}
+
+ {ALL_ICONS.length} icons from {'. Use:{' '}
+ {'
+
+ No icons match “{search}” +
+ )} +{swatch.hex}
+ | Variant | +Shape | +Use for | +
|---|---|---|
text (default) |
+ Rectangular bar, 16px tall, 4px radius | +Text lines, labels, values | +
badge |
+ Pill shape, 12px radius | +Tags, status badges, chips | +
circle |
+ Fully round | +Avatars, icon placeholders, toggle switches | +
text — default, 16px height, 4px radius
+ badge — pill shape, 12px radius
+ circle — round, for avatars and icons
+ {label || colour}
+ {token.cssVar}
+ {token.computed}
+ | Token | ') + rows.push('Value | ') + if (opts.showDescription) rows.push('Usage | ') + rows.push('
|---|---|---|
${e.cssVar} | `)
+ rows.push(` ${e.value} | `)
+ if (opts.showDescription && e.description) {
+ rows.push(` ${e.description} | `) + } + rows.push('
', + ' Dark mode overrides use stronger opacity (0.20-0.40 vs 0.05-0.20).', + ' Higher elevation surfaces should use lighter backgrounds', + ' (--color-surface-emphasis) rather than relying solely on shadows.', + '
', + '', + '', + ' Combine a duration with an easing: transition: all var(--duration-normal) var(--easing-standard).', + ' Use --easing-entrance for elements appearing, --easing-exit for elements leaving.', + '
', + '