Design System Standards
Apple-level design token conventions for cross-platform visual consistency. All visual properties
derive from tokens — no hardcoded values.
The four rules that apply everywhere
- No hardcoded colors in component files — no hex, no
rgb(), no hsl() literals. Consume
tokens via utility classes (bg-primary, text-foreground) or CSS custom properties.
- No arbitrary Tailwind values —
p-[13px], text-[17px], bg-[#ff0000] are all rejected.
Snap to the nearest scale token. If no token fits, question the design first.
- Every interactive element has a visible focus indicator — 2px ring, ≥3:1 contrast, using the
ring-ring token and the focus-visible: prefix (never bare focus:).
- Every animation has a reduced-motion path —
motion-safe: on the web, useReducedMotion()
in JS-driven animation.
// The canonical component line: tokens, focus-visible, guarded motion.
className="bg-primary text-primary-foreground motion-safe:transition-colors motion-safe:duration-150
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
Token naming
All CSS custom properties follow --{category}-{name}:
| Category |
Pattern |
Examples |
| Colors |
Semantic name |
--primary, --secondary, --accent, --success |
| Foreground |
{color}-foreground |
--primary-foreground, --error-foreground |
| Surface |
Surface role |
--background, --card, --popover, --muted |
| Border |
Border role |
--border, --input, --ring |
Colors are declared as space-separated HSL channels with no hsl() wrapper, so Tailwind's
opacity modifier works. Every background token has a contrast-verified -foreground pair.
--primary: 222.2 47.4% 11.2%; /* definition */
background-color: hsl(var(--primary) / 0.5); /* consumption — Tailwind: bg-primary/50 */
Tokens live in :root; dark mode overrides use the .dark class, not
@media (prefers-color-scheme).
Color
- Contrast: ≥4.5:1 for normal text; ≥3:1 for large text (18px+, or 14px+ bold) and UI components.
- Never convey meaning through color alone — semantic colors pair with an icon and a text label.
- Required palettes: Core (
primary/secondary/accent), Neutral (neutral/muted/background/
foreground), Semantic (success/warning/error/info), Surface (card/popover), Border
(border/input/ring).
Typography
Use Tailwind's built-in scale exclusively — no arbitrary font sizes.
text-xs |
text-sm |
text-base |
text-lg |
text-xl |
text-2xl |
text-3xl |
text-4xl |
text-5xl |
| 12px |
14px |
16px (body) |
18px |
20px |
24px |
30px |
36px |
48px |
- Maximum 2 font families per project (sans + mono, or sans + serif).
- Weights: 300, 400, 500, 600, 700 only.
- Line height:
leading-tight (1.25), leading-snug (1.375), leading-normal (1.5),
leading-relaxed (1.625).
Spacing
All spacing is a multiple of 4px, from the Tailwind scale.
| Scale |
Value |
Usage |
0.5-1.5 |
2-6px |
Tight inner padding, icon gaps |
2-4 |
8-16px |
Standard padding, element gaps |
5-8 |
20-32px |
Section padding, card padding |
10-16 |
40-64px |
Major section separation |
20-24 |
80-96px |
Page-level spacing, hero padding |
Padding by atomic level: Atoms p-1-p-3 · Molecules p-2-p-4 · Organisms p-4-p-8 ·
Templates/Pages p-6-p-16.
Motion
Durations: duration-75 (instant feedback) · duration-100 (hover) · duration-150 (default) ·
duration-200 (press, focus) · duration-300 (dropdowns, tooltips) · duration-500 (page
transitions, modals). Ceiling is 500ms — longer feels sluggish.
Easing: ease-out for entering, ease-in for exiting, ease-in-out as default,
cubic-bezier(0.34, 1.56, 0.64, 1) spring for tactile elements (toggles, modals).
Component styling
Multi-variant components use class_variants (Ruby/Phlex) or cva (TypeScript) — never hand-rolled
conditional string concatenation. Five standard axes: size (sm/md/lg/xl), variant
(primary/secondary/outline/ghost/destructive), state (default/hover/active/
disabled/loading), radius (none/sm/md/lg/full), density (compact/default/
comfortable).
Cross-platform
| Platform |
Token source |
Consumption |
| Vite SPA / Next.js |
CSS custom properties |
Tailwind utility classes |
| React Native |
Theme context |
useTheme() hook + StyleSheet |
| Phlex (Rails) |
CSS custom properties |
Tailwind classes + class_variants |
Token names, the 4px spacing base, and type scale ratios are identical on every platform. Touch
targets: 44×44px minimum on mobile, 32×32px on web (WCAG 2.5.8).
Deep guides (read on demand, do not preload)
- Adding a color token, Tailwind wiring, dark mode, contrast verification →
references/defining-tokens.md
cva / class_variants components, focus rings, arbitrary-value escape hatch → references/component-variants.md
- Shared token package, React Native theme, touch targets, parity tests →
references/cross-platform-parity.md
- Framer Motion, Reanimated,
prefers-reduced-motion, animation budgets → references/motion.md
1---2name: std-design-system3description: Design system / token standards — color, typography, spacing, motion tokens; component styling; cross-platform consistency. Use when styling components or defining tokens.4---56# Design System Standards78Apple-level design token conventions for cross-platform visual consistency. **All visual properties9derive from tokens — no hardcoded values.**1011## The four rules that apply everywhere12131. **No hardcoded colors** in component files — no hex, no `rgb()`, no `hsl()` literals. Consume14 tokens via utility classes (`bg-primary`, `text-foreground`) or CSS custom properties.152. **No arbitrary Tailwind values** — `p-[13px]`, `text-[17px]`, `bg-[#ff0000]` are all rejected.16 Snap to the nearest scale token. If no token fits, question the design first.173. **Every interactive element has a visible focus indicator** — 2px ring, ≥3:1 contrast, using the18 `ring-ring` token and the `focus-visible:` prefix (never bare `focus:`).194. **Every animation has a reduced-motion path** — `motion-safe:` on the web, `useReducedMotion()`20 in JS-driven animation.2122```tsx23// The canonical component line: tokens, focus-visible, guarded motion.24className="bg-primary text-primary-foreground motion-safe:transition-colors motion-safe:duration-15025 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"26```2728## Token naming2930All CSS custom properties follow `--{category}-{name}`:3132| Category | Pattern | Examples |33|------------|------------------------|-----------------------------------------------------|34| Colors | Semantic name | `--primary`, `--secondary`, `--accent`, `--success` |35| Foreground | `{color}-foreground` | `--primary-foreground`, `--error-foreground` |36| Surface | Surface role | `--background`, `--card`, `--popover`, `--muted` |37| Border | Border role | `--border`, `--input`, `--ring` |3839Colors are declared as **space-separated HSL channels with no `hsl()` wrapper**, so Tailwind's40opacity modifier works. Every background token has a contrast-verified `-foreground` pair.4142```css43--primary: 222.2 47.4% 11.2%; /* definition */44background-color: hsl(var(--primary) / 0.5); /* consumption — Tailwind: bg-primary/50 */45```4647Tokens live in `:root`; dark mode overrides use the **`.dark` class**, not48`@media (prefers-color-scheme)`.4950## Color5152- **Contrast**: ≥4.5:1 for normal text; ≥3:1 for large text (18px+, or 14px+ bold) and UI components.53- **Never convey meaning through color alone** — semantic colors pair with an icon and a text label.54- Required palettes: Core (`primary`/`secondary`/`accent`), Neutral (`neutral`/`muted`/`background`/55 `foreground`), Semantic (`success`/`warning`/`error`/`info`), Surface (`card`/`popover`), Border56 (`border`/`input`/`ring`).5758## Typography5960Use Tailwind's built-in scale exclusively — no arbitrary font sizes.6162| `text-xs` | `text-sm` | `text-base` | `text-lg` | `text-xl` | `text-2xl` | `text-3xl` | `text-4xl` | `text-5xl` |63|-----------|-----------|-------------|-----------|-----------|------------|------------|------------|------------|64| 12px | 14px | 16px (body) | 18px | 20px | 24px | 30px | 36px | 48px |6566- **Maximum 2 font families** per project (sans + mono, or sans + serif).67- **Weights**: 300, 400, 500, 600, 700 only.68- **Line height**: `leading-tight` (1.25), `leading-snug` (1.375), `leading-normal` (1.5),69 `leading-relaxed` (1.625).7071## Spacing7273All spacing is a multiple of **4px**, from the Tailwind scale.7475| Scale | Value | Usage |76|-------------|---------|-----------------------------------------|77| `0.5`-`1.5` | 2-6px | Tight inner padding, icon gaps |78| `2`-`4` | 8-16px | Standard padding, element gaps |79| `5`-`8` | 20-32px | Section padding, card padding |80| `10`-`16` | 40-64px | Major section separation |81| `20`-`24` | 80-96px | Page-level spacing, hero padding |8283Padding by atomic level: Atoms `p-1`-`p-3` · Molecules `p-2`-`p-4` · Organisms `p-4`-`p-8` ·84Templates/Pages `p-6`-`p-16`.8586## Motion8788Durations: `duration-75` (instant feedback) · `duration-100` (hover) · `duration-150` (default) ·89`duration-200` (press, focus) · `duration-300` (dropdowns, tooltips) · `duration-500` (page90transitions, modals). **Ceiling is 500ms** — longer feels sluggish.9192Easing: `ease-out` for entering, `ease-in` for exiting, `ease-in-out` as default,93`cubic-bezier(0.34, 1.56, 0.64, 1)` spring for tactile elements (toggles, modals).9495## Component styling9697Multi-variant components use `class_variants` (Ruby/Phlex) or `cva` (TypeScript) — never hand-rolled98conditional string concatenation. Five standard axes: `size` (`sm`/`md`/`lg`/`xl`), `variant`99(`primary`/`secondary`/`outline`/`ghost`/`destructive`), `state` (`default`/`hover`/`active`/100`disabled`/`loading`), `radius` (`none`/`sm`/`md`/`lg`/`full`), `density` (`compact`/`default`/101`comfortable`).102103## Cross-platform104105| Platform | Token source | Consumption |106|---------------------|------------------------|-------------------------------------|107| Vite SPA / Next.js | CSS custom properties | Tailwind utility classes |108| React Native | Theme context | `useTheme()` hook + `StyleSheet` |109| Phlex (Rails) | CSS custom properties | Tailwind classes + `class_variants` |110111Token **names**, the 4px spacing base, and type scale ratios are identical on every platform. Touch112targets: 44×44px minimum on mobile, 32×32px on web (WCAG 2.5.8).113114## Deep guides (read on demand, do not preload)115116- Adding a color token, Tailwind wiring, dark mode, contrast verification → `references/defining-tokens.md`117- `cva` / `class_variants` components, focus rings, arbitrary-value escape hatch → `references/component-variants.md`118- Shared token package, React Native theme, touch targets, parity tests → `references/cross-platform-parity.md`119- Framer Motion, Reanimated, `prefers-reduced-motion`, animation budgets → `references/motion.md`