Agent-consumable brand guide
VLLNT UI Design Guide
The canonical rules for brand, tokens, layout, motion, accessibility, and component composition across the library.
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. The full
machine-readable token set lives in packages/design/tokens.json, is documented
in packages/design/README.md, and is mirrored publicly at /r/design.json.
The site rendering at /design reads this file directly.
1. Brand
| Attribute | Value |
|---|---|
| Name | VLLNT UI (always two words, both capitalised) |
| Voice | Direct. No marketing fluff. Short sentences. |
| Mission | Agent-first React components — copy-paste, you own them. |
| Tone | Confident, 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"
2. Logo
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)
| Token | Light role | Dark role |
|---|---|---|
--background | Surface | Surface |
--foreground | Primary text | Primary text |
--muted | Subtle surface | Subtle surface |
--muted-foreground | Secondary text | Secondary text |
--border | Hairline divider | Hairline divider |
--ring | Focus ring | Focus ring |
--accent / --accent-foreground | Active / hover surface | Same |
--destructive / --destructive-foreground | Danger surface / text | Same |
Anti-patterns
- Never use
bg-black/text-white/bg-white. Usebg-background/bg-foreground/text-foregroundso 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 AA minimum. Body text against any surface ≥ 4.5:1, large text ≥ 3:1, non-text UI ≥ 3:1. The default theme passes; new themes must verify before merging.
4. Typography
| Token | Size | Weight | Line height |
|---|---|---|---|
| Display (H1) | 3rem (48px) | 600 (semibold) | 1.1 |
| H2 | 2rem (32px) | 600 | 1.2 |
| H3 | 1.5rem (24px) | 600 | 1.25 |
| H4 | 1.25rem (20px) | 600 | 1.3 |
| Body large | 1.125rem (18px) | 400 | 1.6 |
| Body | 1rem (16px) | 400 | 1.6 |
| Body small | 0.875rem (14px) | 400 | 1.5 |
| Caption | 0.75rem (12px) | 500 | 1.4 |
| Code | inherit | 400 mono | 1.5 |
Rules
- Headings use
font-semibold(600), neverfont-bold(700+) at display sizes — letter shapes crush. - 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.
| Token | Value | Use |
|---|---|---|
space-1 | 4px | Icon padding |
space-2 | 8px | Tight stack, inline gap |
space-3 | 12px | Comfortable inline gap |
space-4 | 16px | Default block stack |
space-6 | 24px | Section break |
space-8 | 32px | Major section |
space-12 | 48px | Page section |
space-16 | 64px | Hero / landing block |
Rules
- Stay on the scale. Never
gap-[7px]etc. - One axis at a time on flex children — use
gap-y-Norgap-x-N, notgap-Nwhen only one axis is needed.
6. Radius
| Token | Value |
|---|---|
radius-none | 0 |
radius-sm | 4px |
radius-md | 8px (default) |
radius-lg | 12px |
radius-full | 9999px |
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.
| Token | Value |
|---|---|
shadow-none | none |
shadow-sm | 0 1px 2px rgba(0,0,0,0.05) |
shadow-md | 0 4px 6px -1px rgba(0,0,0,0.1) |
shadow-lg | 0 10px 15px -3px rgba(0,0,0,0.1) (popovers, dialogs) |
No coloured shadows. No multi-layered glow effects.
8. Motion
Principles
- Purposeful — every animation must explain a state change. No decoration.
- Fast — under 200ms unless the motion communicates distance or volume.
- Subtle — translate < 8px, scale ±2%. No bounces (
springeasings strip fidelity at small distances).
| Token | Duration | Easing | Use |
|---|---|---|---|
duration-fast | 100ms | ease-out | Micro-interactions (button press) |
duration-base | 200ms | ease-out | Open / close, hover, focus |
duration-slow | 300ms | ease-in-out | Layout 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: spinon 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
| Use | Pattern |
|---|---|
| Inline action | Button |
| Single binary choice | Switch |
| One of N | Select (≤ 8 options) or Combobox (> 8 / searchable) |
| Many of N | MultiSelect |
| Free text | Input (single line) or Textarea |
| Confirmation | AlertDialog (destructive) or Dialog (informational) |
| Side panel | Sheet |
| Hover help | Tooltip (≤ 80 chars) |
| Stepper / wizard | Stepper + MultiStepForm |
| Tabular data | DataTable |
| Hierarchical data | TreeView |
| Status chip | Badge |
| Notification (transient) | Toast |
| Notification (persistent) | Banner |
| Empty state | EmptyState |
Anti-patterns
<div onClick>— always useButtonfor click semantics.- Tooltips on disabled buttons — wrap in a
<span>witharia-disabledinstead, since browsers swallow events ondisabledelements. - 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 (
--ringtoken), neveroutline-nonewithout a replacement. - Form inputs always labelled (
<label>oraria-label). - Live regions for async state changes (
aria-live="polite"for toasts,assertivefor 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.jsonper #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)
| Pattern | Why banned |
|---|---|
font-bold on display headings | Crushes letter shapes; use font-semibold |
bg-black / bg-white | Breaks dark mode; use bg-background / bg-foreground |
Tailwind palette literals (bg-zinc-500) | Bypasses tokens; breaks theming |
| Bounce easings | Visual noise; doesn't scale to small distances |
<div> with onClick | Loses keyboard + a11y semantics; use Button |
| Em-dash chains in copy (3+ in a row) | Unreadable |
Submit / OK / Click here button labels | Don't name the action |
| Pure black backgrounds | Eye strain in dark mode |
| Auto-play hero video | Performance + motion sensitivity |
| Modal on modal | Restructure the flow |
eslint-disable to bypass a rule | Fix the root cause |
14. Roadmap (this file)
This document is the v1 brand baseline for VLLNT UI. Expansions tracked separately:
packages/design/tokens.json— machine-readable tokens (same source of truth, JSON form). Follow-up./designsite route — human-browsable rendering of this file with live token previews. Follow-up./r/design.json— JSON endpoint mirroring tokens for agent consumption. Follow-up.meta.jsona11y schemas per component (#255) — keyboard / ARIA / focus rules surfaced via the registry.