Universal Design System

Governance

Universal quality gates that apply to ALL archetypes — Harbor, Sentinel, Current, Lattice, Meridian. Paste this before any archetype spec. Two pastes per prompt, non-negotiable.

Version v1.1 Updated 2026-04-27 Audience: designers, engineers, PMs WCAG 2.1 AA minimum §0–§20 · 21 sections
How to use: Paste this governance spec first into any AI prompt that generates components, tokens, or a spec page (Prompts 1, 45, 46, 47, 49, 51). Then paste your target archetype spec second. Every archetype inherits all gates below and may only add overrides — never remove gates.
§1

Accessibility Gates — WCAG 2.1#

Contrast ratio requirements

  • Body text: ≥ 4.5:1 (AA minimum), aim for ≥ 7:1 (AAA) on primary body copy
  • Large text (18pt+ or 14pt bold): ≥ 3:1 (AA)
  • Non-text UI elements (icons, focus rings, chart elements): ≥ 3:1
  • Decorative-only elements: exempt from contrast requirements
  • Failing combinations must be flagged "FAIL — do not use" with an alternative pairing suggested

Accessibility Verification Table

Every spec page MUST include this table computed from actual token values. Do not omit. Do not estimate.

Pair name FG BG Ratio AA body (4.5:1) AA large (3:1) AAA (7:1)
Body text on page bg #1A1B1E #FFFFFF 18.1:1 Pass Pass AAA
Muted text on page bg #495057 #FFFFFF 7.4:1 Pass Pass AAA
Accent link on page bg #0066CC #FFFFFF 5.9:1 AA Pass Fails AAA
Faint text on page bg #868E96 #FFFFFF 3.0:1 FAIL — body use Large only Fails AAA
White on accent (#0066CC) #FFFFFF #0066CC 5.9:1 AA Pass Fails AAA
Error text on error bg #C92A2A #FFF5F5 6.1:1 AA Pass Fails AAA
Faint text (#868E96 on white) fails AA for body text. Use only for metadata, timestamps, or decorative labels that are not the primary information carrier. Always provide an accessible alternative.
§2

Keyboard Navigation#

  • Every interactive element reachable via Tab in DOM order
  • Focus indicator visible: 2px outline, offset 2px, using --color-focus-ring (defined per archetype)
  • Use :focus-visible, NOT :focus — suppress ring for pointer users
  • Focus trapping required for modal dialogs, drawers, command palettes — Esc returns focus to trigger
  • Skip-to-content link required at the top of every page

Standard key bindings — full map

KeyActionContext
Enter / SpaceActivate elementButtons, toggles, links, checkboxes, radio buttons
EscClose / dismissDialogs, drawers, popovers, menus, command palettes
Arrow KeysNavigate within widgetTabs, radio groups, menus, listboxes, data grids, sliders
Home / EndJump to first / last itemLists, menus, sliders, tab rows
TabMove focus forwardAll focusable elements in DOM order
Shift+TabMove focus backwardAll focusable elements in reverse DOM order
Page Up / Page DownLarge-step incrementSliders, scrollable regions, data tables
F6Move between panesMulti-pane layouts (Lattice especially)
Ctrl/Cmd+KOpen search / command paletteAny product with global search (required in Meridian)
§3

Screen-Reader Requirements#

  • All icon-only buttons must have aria-label
  • All form fields must have programmatic labels: label[for] or aria-labelledby — never placeholder-as-label
  • Live regions for toasts, async results, validation errors: aria-live="polite" default; "assertive" only for errors blocking task completion
  • Decorative SVGs: aria-hidden="true". Meaningful SVGs: role="img" + <title>

Test matrix

Screen readerBrowserPlatformRequired for
VoiceOverSafarimacOS / iOSAll components
NVDAFirefoxWindowsAll components (minimum)
TalkBackChromeAndroidAny component shipped to mobile (Current archetype)
JAWSChrome / EdgeWindowsEnterprise products (Sentinel archetype)
§4

Motion & Animation#

  • prefers-reduced-motion: reduce → fall back to fade-only or no animation. Never just shorten duration — remove transform animation entirely.
  • No autoplay video or animated backgrounds without explicit user consent
  • Vestibular safety: avoid parallax > 20% offset, scroll-jacking, spinning loaders > 2s without a text label
The prefers-reduced-motion rule applies to ALL animations — CSS transitions, JavaScript animations, Lottie files, CSS transforms. Shortening duration is not an acceptable fallback; the transform must be removed entirely for vestibular safety.
§5

Token Hygiene#

  • Zero raw hex in component CSS — every color, size, and shadow goes through var(--token-name)
  • Components reference SEMANTIC tokens, never primitives
  • Both light and dark mode tokens defined for every semantic token, even if a mode is unshipped
  • Tokens declared in a single :root + [data-theme="dark"] override block — never inside component scope
Do — semantic token
color: var(--color-link);
background: var(--color-surface);
border: 1px solid var(--color-border);
References a semantic role. Works in both light and dark themes automatically.
Don't — primitive token / raw hex
color: var(--color-blue-500);
background: #F8F9FA;
border: 1px solid rgba(0,0,0,0.1);
Bypasses theming. Raw hex breaks dark mode. Primitive steps expose implementation.
§6

Component State Coverage#

Every interactive component must implement these 8 states — visually distinct, not just functionally present.

default
hover
focus-visible
active
disabled
loading
error
success

Form field additional states

Form fields additionally implement: empty, filled, required, read-only.

StateVisual treatmentARIA attribute
emptyPlaceholder text at --color-text-faint
filledValue text at --color-text
requiredLabel indicator (per archetype); "(required)" text preferred over asterisk-onlyrequired
read-only--color-surface-2 background; no focus ringreadonly / aria-readonly="true"
error--color-error border; error message belowaria-invalid="true" + aria-describedby
§7

Error Message Formula#

Every error message follows What → Why → How.

What: [what happened, plain language]
Why: [the cause]
How: [recovery action — concrete, doable]
Do — What/Why/How
"Email format isn't recognized. Try name@example.com."
Don't — generic
"Invalid input."
Do — cause + recovery
"Upload failed. The file is over 10MB. Try compressing it or choose a smaller file."
Don't — vague retry
"Something went wrong. Try again."
Do — specific action
"Session expired. Sign in again to continue where you left off."
Don't — Oops voice
"Oops! Looks like something went sideways. Whoops!"

Banned error patterns

  • Stack traces in user-facing copy
  • Error codes without explanation (e.g. "Error 403" with no context)
  • Generic "Oops!" / "Whoops!" / "Uh-oh!" messaging in product UI
§8

Copy Hygiene#

  • Sentence case for headings, buttons, labels — not Title Case, not ALL CAPS (except overline/eyebrow style)
  • No emoji in UI copy — SVG icons only (emoji acceptable in user-generated content rendering)
  • Contractions allowed (we'll, don't, can't) — softer voice
  • Verb-first CTAs: "Save changes" not "Changes can be saved"

Banned copy patterns

Banned filler words
"Please enter your email." / "Just click here." / "Simply upload your file." / "Kindly confirm."
Banned marketing fluff in product UI
"Let's embark on your journey." / "Unlock the power of analytics." / "Revolutionize your workflow."
§9

Iconography Floor#

  • SVG only. Inline SVG preferred for color inheritance
  • fill="currentColor" or stroke="currentColor" — never hardcoded hex on icon paths
  • aria-hidden="true" for decorative icons; <title> for meaningful icons
  • Consistent grid: 24px standard; archetype may override (Lattice uses 16px dense)
  • Consistent stroke width within a family — never mix stroke widths on the same surface
  • Never mix icon families on the same surface

Banned icon patterns

  • Emoji used as icon in product UI
  • Raster (.png) icons — not theme-aware, not scalable
  • Animated GIF icons — motion not controllable by user
§10

Versioning — SemVer#

All design system releases follow MAJOR.MINOR.PATCH semantic versioning.

MAJOR
Breaking token rename, removed component, breaking API change. Deprecated items removed after 2 MINOR versions.
MINOR
New component, new variant, new token, deprecation notice. Deprecated tokens log console warnings at build time.
PATCH
Bug fix, doc update, non-visual refactor. Never introduces breaking changes.
Deprecation policy: deprecated tokens/components ship for 2 MINOR versions before removal. Deprecation warnings must be logged in the console at build time so teams have advance notice.
§11

Changelog Template#

Every release entry follows this canonical structure. A11y improvements are called out separately.

## [VERSION] — YYYY-MM-DD

### Added
- [new components, tokens, variants]

### Changed
- [modifications to existing surfaces]

### Deprecated
- [old → new migration path, removal version]

### Removed
- [breaking removals — MAJOR only]

### Fixed
- [bug fixes]

### A11y
- [accessibility improvements — call out separately]
§12

Quality-Gate Checklist#

All 12 items are PR / release blocking. A release cannot ship until every gate is green.

  • Contrast ratios verified (AA minimum, AAA for primary body)
  • Keyboard navigable (Tab / Space / Enter / Esc / Arrows all wired)
  • Screen-reader tested (VoiceOver + NVDA minimum)
  • prefers-reduced-motion honored (transform animations removed, not just shortened)
  • Both light + dark mode tokens defined for every semantic token
  • Storybook (or equivalent) story per variant + state
  • No raw hex in component CSS (all colors via semantic tokens)
  • No emoji in product UI (SVG icons only)
  • Component implements all 8 states: default / hover / focus-visible / active / disabled / loading / error / success
  • Error messages follow What/Why/How formula
  • Visual-regression snapshot committed and passing
  • Changelog entry filed under correct SemVer bump
§13

Spec-Page Output Requirements (Prompt 49)#

Every generated spec page must contain these sections in this order, even if the archetype spec doesn't enumerate them explicitly. Prompt 49 derives missing details from archetype rules.

SectionNameNotes
§1.1Color system + Accessibility verification tableMust enumerate every text/background pairing with computed ratios
§1.2TypographyScale, weights, line-heights — all from tokens
§1.3Spacing scaleAll 8–12 steps with use-case notes
§1.4Elevation + shadowsUse case per token
§1.5Shape & bordersRadii with use guidance per component category
§1.6MotionDurations, easings, reduced-motion fallback
§1.7IconographyFamily, grid, stroke, visual sample grid
§2Voice & contentDo/Don't pairs (minimum 6 pairs)
§3Component libraryFoundation → composite → domain progression
§4PatternsForm layout, empty state + archetype-specific patterns
§5AccessibilityWCAG conformance target, keyboard map, SR notes
§6Versioning + GovernanceQuality-gate checklist + changelog template
FooterVersion · date · mode · WCAG target · archetype nameRequired on every generated page
§15

Performance Budgets New#

Performance is a design system concern. Slow systems erode every other quality choice. These are the universal floor targets — every archetype must meet them at a minimum.

Universal targets (every archetype, every page)

FCP
< 1.5s
First Contentful Paint
on 4G mobile
LCP
< 2.5s
Largest Contentful Paint
on 4G mobile
CLS
< 0.1
Cumulative Layout Shift
universal
TTI
< 3.5s
Time to Interactive
on 4G mobile
JS Bundle
< 100KB
Initial route, gzipped
CSS Bundle
< 30KB
Total CSS, gzipped
Web Fonts
< 100KB
Total (subset + woff2)
Largest Image
< 200KB
WebP / AVIF preferred

Per-archetype tightening (overrides to universal floor)

ArchetypeOverrideRationale
Current Initial JS < 70KB Mobile networks; battery constraints; launch speed is UX for native-feel apps
Lattice Initial JS < 50KB; data-grid virtualization mandatory Dense data views must stay fast at scale; virtualization prevents DOM bloat
Meridian Web font < 60KB; first paint shows body text within 1s Reading products: body text is the LCP element — it must appear immediately
Harbor Hero image < 150KB; font subset to display set only Marketing first impressions; large unoptimized heroes destroy perceived quality
Sentinel Initial JS < 100KB (workhorse default) Enterprise desktop apps have more headroom but must stay auditable
Quality-gate addition: every release runs Lighthouse + WebPageTest budget check. Performance regressions fail CI — same priority as contrast failures or broken keyboard navigation.
§16

Internationalization (i18n) New#

Every component must work across writing directions, character ranges, and locale-sensitive data formats. i18n failures are a11y failures.

Writing direction

DirectionLanguagesRequirement
LTR (default) English, French, German, Spanish, most European Default layout; baseline requirement
RTL Arabic, Hebrew, Persian, Urdu Every component must declare logical CSS properties so RTL flips correctly without per-language overrides
Use logical CSS properties throughout: margin-inline-start instead of margin-left, padding-block-end instead of padding-bottom, inset-inline-end instead of right. This is the mechanism that enables RTL without separate stylesheets.

Character ranges

  • Latin (basic + extended): default font stack covers this
  • CJK (Chinese, Japanese, Korean): taller line-heights needed (1.6+ minimum); fonts may need to swap to region-appropriate (Noto Sans CJK). Never assume Latin character widths.
  • Cyrillic, Greek, Devanagari: support per product market reach; verify font coverage before shipping

Locale-sensitive tokens — use the Intl API

// Date/time — never hardcode "MM/DD/YYYY"
new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date);

// Numbers — handles locale separators, currency, units
new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(1234.56);
// → en-US: $1,234.56  |  de-DE: 1.234,56 $  |  ja-JP: $1,234.56

// Sort order — use Intl.Collator for string comparison
new Intl.Collator(locale, { sensitivity: 'base' }).compare(a, b);

// Pluralization — use ICU MessageFormat, not string concatenation
// BAD:  "You have " + count + " items"
// GOOD: new Intl.PluralRules(locale).select(count)
//       → MessageFormat("{count, plural, one {# item} other {# items}}")

Banned i18n antipatterns

  • Hardcoded English strings in components — use i18n keys
  • Concatenated strings ("You have " + count + " items") — use ICU plurals
  • Fixed-width buttons that truncate German text (German averages 30%+ longer than English)
  • Direction-aware icons (chevrons, arrows, play buttons) without RTL flip
  • Assuming Latin characters in regex / validation patterns
  • ASCII-only password rules (blocks non-Latin users from using their native characters)
§17

Form Validation Strategy New#

Validation behavior is a tonal choice — get it wrong and the form feels nagging or surprises the user at submit time.

When to validate

TriggerPolicyUse cases
On blur Default All general input fields — user has completed their thought before feedback arrives
On submit Required — always runs Final validation pass on all fields; catches anything missed by blur validation
On change (real-time) Allowed for specific cases only Password strength meters, username availability (debounced API), password confirm match
On every keystroke Banned for general fields Feels nagging; user is mid-thought; breaks flow for slower typists and screen reader users

What to validate

  • Required field empty
  • Format mismatch (email, URL, phone, date)
  • Range / length violations (min/max characters, min/max values)
  • Server-side rules (uniqueness, conflict, capacity limits)

How to display validation errors — the canonical pattern

<!-- What: field border turns error color -->
<!-- Why: aria-invalid tells screen readers the value is wrong -->
<!-- How: error message below with icon + text = redundant encoding -->

<label for="email">Email</label>
<input
  type="email"
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
  style="border-color: var(--color-error);"
>
<div id="email-error" role="alert" aria-live="polite">
  <!-- Icon (color + icon = colorblind-safe redundant encoding) -->
  <svg aria-hidden="true">...error icon...</svg>
  <!-- What / Why / How message -->
  Email format isn't recognized. Try name@example.com.
</div>

<!-- Submit stays ENABLED — disabling hides the form's validation
     state from the user (WCAG SC 3.3.4 violation) -->
<button type="submit">Create account</button>

Display rules

  • Error message replaces helper text below the input (same slot)
  • Field gets --color-error border + 3px focus ring when focused
  • Error message follows the What/Why/How formula (§7)
  • Error icon to the left of message — color + icon = redundant encoding for colorblind accessibility
  • Submit button stays enabled — disabling submit hides validation state from the user

Per-archetype overrides

ArchetypeOverride
SentinelInline validation on blur; "(required)" parenthetical text, not asterisk-only
CurrentKeyboard type matches input semantic: inputMode="numeric" / "email" / "tel"
MeridianForms are rare; when present, generous spacing (--space-4 between fields), clear labels above
LatticeNumber-field formatting uses mono + tabular figures; Tab key advances within table-grid forms
HarborLabels always above (never floating); error tone consistent with editorial voice — no exclamation marks
§18

Loading-State Strategy New#

Choose the right loading affordance — wrong choice feels broken. Operation duration drives the decision, not developer preference.

Decision tree: operation duration → preferred loading state

< 100ms
No loading state (instant)
User won't notice. Showing a spinner adds latency perception, not removes it. Apply immediately.
100–300ms
Optimistic UI
Immediate state change; server confirms in background; rollback on error. Right for: like buttons, toggle switches, inline edits.
300ms–1s
Button loading state
Button shows spinner inline + disables itself; rest of page stays interactive. User knows the action is processing.
1s–5s
Skeleton placeholders (NOT spinner)
Show shape of incoming content. Reduces perceived wait. Skeleton MUST match content shape — generic rectangles are banned.
5s–30s
Progress indicator + text label
"Analyzing screenshot — 68% (about 4 seconds left)". Shows progress + ETA + ability to cancel.
> 30s
Background processing + completion notification
Don't make the user wait at the screen. Submit, navigate away, notify via toast / email / push when done.

Per-archetype tightening

ArchetypeOverride
LatticePrefer NO loading state for sub-100ms ops; never hide chrome behind a loader (keep keyboard shortcuts + nav functional at all times)
CurrentPull-to-refresh with haptic on threshold; skeleton required for any list waiting > 500ms
MeridianSkeletons banned (motion ban applies); use opacity-static placeholders OR no loader at all (most docs render statically)
HarborSkeletons subtle (--color-surface-2 base, opacity pulse); never shimmer animation (too feature-heavy for editorial)
SentinelText label required next to spinner if wait > 1s — ambiguity is not acceptable in productivity tools

Banned loading antipatterns (universal)

  • Spinner with no text after 1s — ambiguous: is it stuck or working?
  • Full-screen blocker for partial-page loads
  • Skeleton shimmer in Meridian / editorial contexts (violates motion policy)
  • Hide nav / sidebar / chrome behind a loading state — keyboard navigation must remain functional
  • Disabling Submit button "to prevent double-click" — use debounce + button loading state instead
§19

Hybrid Products New#

Most real products are hybrids. One archetype is rarely sufficient for an entire product surface. These are the five most common patterns.

Marketing site + Product app
Examples: Linear, Notion, Stripe, Vercel
Harbor for the marketing surface (homepage, pricing, case studies); Sentinel for the in-app product. Separate codebases or distinct route trees; each ships its own tokens.css with shared brand primitives but independent semantic layers, type scales, and density.
Web app + Mobile app
Examples: Slack, Figma, Notion, Asana
Sentinel for desktop web; Current for native mobile. Shared brand color anchor; otherwise distinct systems. Don't try to "responsive" Sentinel down to mobile — touch ergonomics demand Current's rules (44px targets, bottom-anchored nav, sheets-not-modals).
Product app + Data terminal view
Examples: Linear command bar, Datadog, Snowflake worksheet
Sentinel for the workflow shell; Lattice for data-explorer / monitoring screens. Keep Lattice scoped to specific routes. Transitioning into the Lattice route changes chrome density + introduces the data-system color palette — make the context shift explicit.
Marketing site + Documentation
Examples: Stripe, Tailwind, Vercel
Harbor for marketing (root domain); Meridian for docs (/docs or docs.* subdomain). Shared header brand, distinct layouts. Docs reading experience must NOT inherit Harbor's atmospheric hero patterns — readers came to find answers, not to be marketed to.
Product app + In-app docs / help center
Examples: Notion help, Linear docs, Figma Community
Sentinel shell + Meridian content panes for docs. Docs render as a Meridian-styled panel inside Sentinel's chrome (right drawer, modal, or dedicated route). The panel is a contained Meridian environment — it does not share Sentinel's density or spacing tokens.
Keep token files separate — the universal hybrid rule

Do not force one tokens.css to cover both archetypes. Each archetype owns its own separate: semantic layer (Layer 2 token names), typography scale, density scale, component priority order, and banned patterns.

Share ONLY:
  • Brand primary hex (the anchor color)
  • Brand voice principles (broad guidelines — not specific voice rules, which vary per archetype)
  • Universal governance gates (this file)
The most expensive mistake in hybrid design: forcing one archetype's choices onto another archetype's use case. A 14px base font is correct for Lattice and catastrophic for Meridian. An animated hero is right for Harbor and wrong for a data terminal. Pick the right archetype per surface, then build entirely within it.
§20

Archetype Overrides — Aggregated Summary New#

Each archetype inherits this governance spec in full and adds its own overrides. Below is the canonical aggregated summary. Updated whenever any archetype's overrides change.

Harbor
Editorial / Marketing systems
9 overrides
  • Warm oklch shadows — any cold rgba shadow fails CI
  • Single accent enforced — any secondary accent ramp fails lint
  • Typography is fluid clamp() — any fixed-px --text-* fails lint
  • Serif italic display reserved for editorial emphasis — no italic body class
  • Floating label class banned — fails component lint
  • Indigo/purple banned in any token — fails token lint
  • Avatar component defaults to initials variant — stock-photo variant fails review
  • One primary CTA per section enforced at template level
  • Hero variants limited to template 01 (Hero Editorial) — no carousel hero
Sentinel
Enterprise productivity / SaaS
10 overrides
  • Two-layer token architecture — component referencing Layer 1 directly fails lint
  • Accent-CTA rule — white text on accent-500 fails CI contrast check
  • Tabular numerals required on all numeric columns (font-feature-settings: "tnum" 1)
  • Typography is fixed px modular — any clamp() on --text-* fails lint
  • Sans-serif only — any --font-serif declaration fails lint
  • Cold rgba shadows enforced — any oklch shadow fails lint
  • Dark mode tokens defined for every Layer 2 semantic token
  • WCAG verification table required in spec page (Prompt 49 must enumerate every pairing)
  • Color-only encoding banned — every indicator combines color + icon + text label
  • "(required)" parenthetical enforced — asterisk-only fails form lint
Current
Native mobile / touch-first
11 overrides
  • 44px minimum touch target — any hit area < 44×44px fails lint
  • Body font-size ≥ 16px on inputs — prevents iOS zoom on focus
  • Native font stack required — custom fonts are opt-in via brand override only
  • Safe-area insets honored on every fixed-position element
  • Hover-only interactions banned — every :hover state requires :focus or pressed equivalent
  • Hamburger nav banned as primary navigation
  • Tab bar capped at 5 items
  • Haptic feedback paired with visible state change — haptic alone fails a11y review
  • Modal dialogs require bottom-sheet alternative consideration
  • keyboardType matches input semantic (inputMode="numeric", "email", "tel")
  • Both light + dark mode required — no "dark mode v2" deferrals
Lattice
Data-dense terminal / analytics
10 overrides
  • Two-color-system architecture — chrome and data palettes cannot share tokens
  • Monospace + tabular figures required on any numeric column (font-feature-settings: "tnum" 1)
  • Every interactive element has a documented keyboard shortcut — icon-buttons without shortcut metadata fail lint
  • Animation on data updates banned (cell flash, row pulse — motion-fatigue)
  • Shadows on default surfaces banned — panes, cells, rows have no shadow
  • Confirmation dialogs banned for reversible actions — use undo snackbar pattern
  • Pagination banned for data tables — must virtualize
  • Color-only encoding banned — every status combines color + icon + text
  • Body type ≥ 11px in chrome (10px only for axis labels, micro-metadata)
  • Pane resizability required — any non-resizable splitter fails workspace lint
Meridian
Documentation / long-form reading
11 overrides
  • Reading width hard-capped at 72ch on body content
  • Body type ≥ 17px — any smaller --text-body fails lint
  • Line-height ≥ 1.7 on body — tighter --leading-body fails lint
  • Animation on body content banned — no transitions on .article-body or descendants
  • Sepia mode + dark mode tokens defined (sepia is Meridian-specific variant)
  • Print stylesheet required — any product without @media print rules fails build
  • All headings (h2+) auto-id'd with anchor link — heading without id fails lint
  • External links flagged with icon — a[href^=http] without external-link indicator warns
  • Code blocks have copy button — any pre>code without copy affordance fails lint
  • Search shortcut Cmd/Ctrl+K wired — any docs product without it fails review
  • Marketing hype words blocked in body ("powerful", "seamless", "revolutionary" etc. fail copy lint)