Guide de marque consommable par les agents

Guide de design VLLNT UI

Les regles canoniques pour la marque, les jetons, la mise en page, le mouvement, l'accessibilite et la composition des composants dans toute la bibliotheque.

DESIGN.md

Single source of truth for VLLNT UI's brand and design conventions. Humans read this file. Agents read it too — every UI suggestion must follow it.

This is the canonical brand & design guideline for the library. Read it on the web at three public surfaces, all generated from this one file:

  • /DESIGN.md — this file as raw markdown (canonical for agents).
  • /design — human-browsable rendering with a contents nav.
  • /r/design.json — machine-readable token set.

The full token set lives in packages/design/tokens.json and is documented in packages/design/README.md.


1. Brand

AttributeValue
NameVLLNT UI (always two words, both capitalised)
VoiceDirect. No marketing fluff. Short sentences.
MissionAgent-first React components — copy-paste, you own them.
ToneConfident, technical, honest about tradeoffs.

Voice rules

  • Sentence case for headings, button labels, tooltips. Title Case only for proper nouns.
  • Avoid superlatives ("the best", "blazingly fast"). State facts.
  • Avoid em-dash chains. One per sentence max.
  • Use … (ellipsis glyph), not ....
  • No emoji in shipped UI. Lucide icons handle anything visual.

Banned phrases

  • "Click here"
  • "Submit" as a button label (use the verb of the action)
  • "Loading…" without a context noun ("Loading components…")
  • "Sign up to learn more"

VLLNT UI set in the system font stack at the brand weight. There is no standalone wordmark file; the type itself is the mark.

Clear space — at minimum, height of the cap-height of the logotype. Min size — 14px equivalent. Below that, omit "UI" and show only "VLLNT". Misuse — never stretch, recolor, italicise, or add a tagline beneath.


3. Color

Tokens live as CSS variables in packages/ui/themes/default.css. Components always consume tokens — never raw hex. The token set is versioned with the library.

Machine clients use packages/design/tokens.json. The JSON schema is documented in packages/design/tokens.schema.json and groups tokens by color, typography, spacing, radius, elevation, motion, and iconography.

Semantic tokens (consumer-facing)

Values are OKLCH channels; the sRGB hex is what renders on an sRGB display.

TokenRoleLightDark
--backgroundPage surface1 0 0 (#ffffff)0 0 0 (#000000)
--foregroundPrimary text0.1445 0 0 (#0a0a0a)0.9848 0 0 (#fafafa)
--card / --popoverContained / floating surface1 0 0 (#ffffff)0.1445 0 0 (#0a0a0a)
--mutedSubtle surface (--secondary and --accent share it)0.9703 0 0 (#f5f5f5)0.2686 0 0 (#262626)
--muted-foregroundSecondary text0.525 0 0 (#6a6a6a)0.7153 0 0 (#a3a3a3)
--destructiveDanger fill and danger text0.55 0.2078 25.326 (#cf1f29)0.7 0.18 25.721 (#fa6961)
--destructive-foregroundText on a destructive fill0.9848 0 0 (#fafafa)0.2044 0 0 (#171717)
--borderDecorative hairline (cards, separators, tables)0.9219 0 0 (#e5e5e5)0.2686 0 0 (#262626)
--inputForm-control boundary0.66 0 0 (#929292)0.49 0 0 (#606060)
--ringFocus ring0.1445 0 0 (#0a0a0a)0.8697 0 0 (#d4d4d4)

In dark mode --destructive is a light red paired with a dark --destructive-foreground (the shadcn v4 pairing). One dark red cannot be both readable text on a near-black surface and a fill behind white text.

Anti-patterns

  • Never use bg-black / text-white / bg-white. Use bg-background / bg-foreground / text-foreground so dark mode works.
  • Never use bg-gray-500 / Tailwind palette literals. Pick a semantic token.
  • Pure black (#000) is banned for backgrounds — use --background.

Contrast

WCAG 2.2 AA minimum: text ≥ 4.5:1 (large text ≥ 3:1), non-text UI such as control boundaries and focus rings ≥ 3:1. The token contract below holds for the default theme and every preset in themes/presets.css, in light and dark. Tests enforce it and fail on any regression: packages/ui/src/lib/theme-contrast.test.ts (web CSS) and packages/ui-core/src/theme.test.ts (native theme).

RulePairsMinimum
On-surface textX-foreground on X for background, card, popover, primary, secondary, accent, destructive4.5:1
Secondary text--muted-foreground on background, card, popover, muted4.5:1
Danger text--destructive on background, card, popover, muted, and on its own 10% tint (bg-destructive/10)4.5:1
Control boundary--input against background, card, popover3:1
Focus ring--ring against background3:1

Default theme ratios: muted-foreground 5.41 (light, on background) / 4.96 (light, on muted), 6.00 (dark, on muted); destructive text 5.41 / 4.96 (light), 7.26 / 5.23 (dark, on background / muted); destructive-foreground on destructive 5.18 (light), 6.20 (dark); input 3.11 (light), 3.34 (dark, on background).

Rules

  • Draw form-control boundaries (input, textarea, select trigger, checkbox, switch off-track, composers) with border-input, never border / border-border. --border is decorative and stays low contrast.
  • Text on bg-primary / bg-secondary / bg-accent / bg-destructive uses that surface's *-foreground. --muted-foreground is only guaranteed on background, card, popover, and muted.
  • Put light text on a destructive fill with text-destructive-foreground, never text-white: dark mode pairs a light red with dark text.
  • New themes and presets must pass the contract test before merging. Keep new values inside the sRGB gamut so the measured ratio matches what renders.

4. Typography

Font families — theme-overridable CSS variables. A brand adopts a different type identity by overriding these on a theme scope; nothing in the library is edited.

FamilyCSS variableDefaultRole
Sans--font-sanssystem sans stackBody + UI text (Text, Prose)
Display--font-displayvar(--font-sans)Headings + hero (Heading, Display) — set a serif/display face here for a brand identity
Mono--font-monosystem mono stackCode, tabular

Type scale — each step is a token (--font-size-*); heading/display weight is a token too (--font-weight-heading, --font-weight-display). Overriding any of these restyles the foundation primitives.

TokenCSS variableSizeWeightLine height
Display--font-size-display3.75rem (60px)600 (--font-weight-display)1.05
H1--font-size-h13rem (48px)600 (--font-weight-heading)1.1
H2--font-size-h22.25rem (36px)6001.2
H3--font-size-h31.875rem (30px)6001.25
H4--font-size-h41.5rem (24px)6001.3
H5--font-size-h51.25rem (20px)6001.4
H6--font-size-h61.125rem (18px)6001.5
Body large--font-size-body-lg1.125rem (18px)4001.7
Body--font-size-body1rem (16px)4001.6
Body small--font-size-body-sm0.875rem (14px)4001.5
Caption--font-size-caption0.75rem (12px)5001.4
Codeinheritinherit400 mono1.5

Rules

  • The Text / Heading / Display / Prose foundation primitives consume the tokens above — style them by overriding tokens, not by forking the components.
  • Headings use font-semibold (600), never font-bold (700+) at display sizes — letter shapes crush. The heading weight token defaults to 600.
  • Code uses the system mono stack via font-mono.
  • Tabular numerals for data tables, charts, transactions: font-variant-numeric: tabular-nums.

5. Spacing

4-pt scale. Tailwind classes map 1:1.

TokenValueUse
space-14pxIcon padding
space-28pxTight stack, inline gap
space-312pxComfortable inline gap
space-416pxDefault block stack
space-624pxSection break
space-832pxMajor section
space-1248pxPage section
space-1664pxHero / landing block

Rules

  • Stay on the scale. Never gap-[7px] etc.
  • One axis at a time on flex children — use gap-y-N or gap-x-N, not gap-N when only one axis is needed.

6. Radius

TokenValue
radius-none0
radius-sm4px
radius-md8px (default)
radius-lg12px
radius-full9999px

Cards, popovers, dialogs use radius-md. Pills, avatars use radius-full. Long surfaces (panels, sheets) use radius-lg.


7. Elevation

Use sparingly. Most components are flat with border + bg-background.

TokenValue
shadow-nonenone
shadow-sm0 1px 2px rgba(0,0,0,0.05)
shadow-md0 4px 6px -1px rgba(0,0,0,0.1)
shadow-lg0 10px 15px -3px rgba(0,0,0,0.1) (popovers, dialogs)

No coloured shadows. No multi-layered glow effects.


8. Motion

Principles

  1. Purposeful — every animation must explain a state change. No decoration.
  2. Fast — under 200ms unless the motion communicates distance or volume.
  3. Subtle — translate < 8px, scale ±2%. No bounces (spring easings strip fidelity at small distances).
TokenDurationEasingUse
duration-fast100msease-outMicro-interactions (button press)
duration-base200msease-outOpen / close, hover, focus
duration-slow300msease-in-outLayout shifts, modal entry

prefers-reduced-motion — when reduce, durations collapse to 1ms or motion is skipped entirely.

Banned

  • Bounce easings (cubic-bezier(0.68, -0.55, 0.265, 1.55)).
  • Auto-playing entrance animations on initial page load (jarring).
  • animation: spin on anything except a true loading spinner.

9. Iconography

  • Library: lucide-react (only). Match its line-art aesthetic.
  • Default size: 16px (h-4 w-4).
  • Stroke: 2px (lucide default).
  • Color: inherits currentColor. Don't hardcode.
  • Spacing: 8px (gap-2) between icon and adjacent text.

10. Component patterns

UsePattern
Inline actionButton
Single binary choiceSwitch
One of NSelect (≤ 8 options) or Combobox (> 8 / searchable)
Many of NMultiSelect
Free textInput (single line) or Textarea
ConfirmationAlertDialog (destructive) or Dialog (informational)
Side panelSheet
Hover helpTooltip (≤ 80 chars)
Stepper / wizardStepper + MultiStepForm
Tabular dataDataTable
Hierarchical dataTreeView
Status chipBadge
Notification (transient)Toast
Notification (persistent)Banner
Empty stateEmptyState

Anti-patterns

  • <div onClick> — always use Button for click semantics.
  • Tooltips on disabled buttons — wrap in a <span> with aria-disabled instead, since browsers swallow events on disabled elements.
  • Modal-on-modal — restructure the flow.
  • Polymorphic children + items props on the same component — pick one.

11. Accessibility (WCAG AA minimum)

  • Every interactive element is keyboard-reachable.
  • Focus rings visible (--ring token), never outline-none without a replacement.
  • Form inputs always labelled (<label> or aria-label).
  • Live regions for async state changes (aria-live="polite" for toasts, assertive for errors).
  • Color is never the only signal — pair with icon, text, or pattern.
  • Animations respect prefers-reduced-motion.
  • Components shipping a defined ARIA pattern (combobox, dialog, menu, tabs) declare their full keyboard map in meta.json per #255.

12. Voice & writing

  • Buttons: name the action. "Save changes" not "Submit", "Send invite" not "OK".
  • Errors: state what happened + what to do. "Couldn't reach the API. Check your connection and retry."
  • Empty states: explain why empty + what unblocks the user. "No components match. Try a different filter or request a component."
  • Inputs: placeholder = example, label = field meaning. They are not interchangeable.
  • Don't hide failures. If something fails silently, you have a bug.

13. Anti-patterns (banned)

PatternWhy banned
font-bold on display headingsCrushes letter shapes; use font-semibold
bg-black / bg-whiteBreaks dark mode; use bg-background / bg-foreground
Tailwind palette literals (bg-zinc-500)Bypasses tokens; breaks theming
Bounce easingsVisual noise; doesn't scale to small distances
<div> with onClickLoses keyboard + a11y semantics; use Button
Em-dash chains in copy (3+ in a row)Unreadable
Submit / OK / Click here button labelsDon't name the action
Pure black backgroundsEye strain in dark mode
Auto-play hero videoPerformance + motion sensitivity
Modal on modalRestructure the flow
eslint-disable to bypass a ruleFix the root cause

14. Surfaces and roadmap

This document is the v1 brand baseline for VLLNT UI. It ships on these surfaces today:

  • /DESIGN.md — this file as raw markdown.
  • /design — human-browsable rendering with a contents nav.
  • /r/design.json — JSON token set, mirroring packages/design/tokens.json.

Still planned:

  • Live token previews inline on the /design page.
  • meta.json a11y schemas per component (#255) — keyboard / ARIA / focus rules surfaced via the registry.

15. References