---
meta:
  name: "Vercel"
  version: "1.0.0"
  updated: "2026-04-19"
  description: "Vercel Geist aesthetic — black-on-white, Geist Sans + Geist Mono, high-contrast minimal chrome, geometry-first layouts. Developer-platform aesthetic without the SaaS gradient."
  inspiration: "vercel.com / @vercel/design"

tokens:
  colors:
    bg: "#FFFFFF"
    surface: "#FAFAFA"
    accent: "#0070F3"
    text: "#000000"
    border: "#EAEAEA"
    status:
      success: "#0070F3"
      warning: "#F5A623"
      danger: "#E00000"
      info: "#0070F3"

  typography:
    families:
      primary: "Geist, Geist Variable, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
      display: "Geist, Geist Variable, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
      mono: "Geist Mono, ui-monospace, 'SF Mono', Menlo, Monaco, monospace"
    scale:
      - "12px"
      - "14px"
      - "16px"
      - "18px"
      - "24px"
      - "32px"
      - "48px"
      - "64px"
      - "96px"
    weights: [400, 500, 600, 700]
    lineHeight:
      tight: "1.1"
      snug: "1.3"
      normal: "1.5"

  spacing:
    - "4px"
    - "8px"
    - "12px"
    - "16px"
    - "24px"
    - "32px"
    - "48px"
    - "64px"
    - "96px"

  radius:
    none: "0px"
    sm: "4px"
    md: "6px"
    lg: "8px"
    xl: "12px"
    full: "9999px"

  shadow:
    sm: "0 1px 2px rgba(0,0,0,0.06)"
    md: "0 4px 12px rgba(0,0,0,0.08)"
    lg: "0 16px 40px rgba(0,0,0,0.12)"

  motion:
    duration:
      fast: "100ms"
      base: "200ms"
      slow: "300ms"
    easing:
      standard: "cubic-bezier(0.4, 0, 0.2, 1)"
      emphatic: "cubic-bezier(0.2, 0, 0, 1.2)"

components:
  button:
    radius: md
    paddingX: "16px"
    paddingY: "8px"
    fontWeight: 500
  card:
    radius: lg
    padding: "24px"
    shadow: sm
  input:
    radius: md
    paddingX: "12px"
    paddingY: "8px"
    borderWidth: "1px"

antiPatterns:
  - id: no-side-stripe-borders
    category: layout
    pattern: "border-left:\\s*(?:[3-9]|\\d{2,})px"
    why: "Side-stripe callouts (>= 3px left borders) are an AI tell — they read as generic CMS snippets, not designed surfaces."
    rewrite: "Use a subtle background tint, a top-aligned icon, or a semantic text color instead of a left stripe."
  - id: no-nested-cards
    category: layout
    pattern: "class=\"[^\"]*rounded-(?:xl|2xl|3xl)[^\"]*shadow[^\"]*\"[^>]*>\\s*<[^>]*class=\"[^\"]*rounded-(?:xl|2xl|3xl)[^\"]*shadow"
    why: "A rounded-shadowed card containing another rounded-shadowed card reads as 'I didn't know how to group these'. Flatten."
    rewrite: "Use section-level whitespace and a single card, or swap the outer card for a plain div."
  - id: no-gradient-text
    category: typography
    pattern: "background-clip:\\s*text"
    why: "Gradient text (background-clip: text) is the single strongest AI aesthetic. It reads as try-hard landing-page filler."
    rewrite: "Use a solid color from tokens.colors.text or tokens.colors.accent. Reserve gradients for non-type surfaces."
  - id: no-inter-only
    category: typography
    pattern: "font-family:\\s*['\"]?Inter['\"]?\\s*[,;]?\\s*$"
    why: "Inter as the sole declared font is the AI-default and signals zero type choice was made."
    rewrite: "Declare a primary family with intentional fallbacks (see tokens.typography.families.primary) or pick a distinctive display face."
  - id: no-all-bold-headings
    category: typography
    pattern: "<h[1-3][^>]*class=\"[^\"]*font-black\\b"
    why: "`font-black` on every heading collapses type hierarchy into shouting. Weight differentiates; shouting doesn't."
    rewrite: "Reserve font-black for one hero moment per page; use semibold/bold for most headings."
  - id: no-uppercase-body
    category: typography
    pattern: "<p[^>]*class=\"[^\"]*uppercase\\b"
    why: "ALL-CAPS body text kills readability. Uppercase belongs on labels and eyebrow text only."
    rewrite: "Use tracking-widest or small-caps on short labels; keep body at normal case."
  - id: no-purple-saas-gradient-hero
    category: color
    pattern: "linear-gradient\\([^)]*#[68][0-9a-f]{2}[a-f0-9]f[a-f0-9]"
    why: "Purple→pink hero gradients are the generic SaaS tell."
    rewrite: "Use a single tokens.colors.accent flood or a neutral photograph."
  - id: no-cyan-on-dark
    category: color
    pattern: "color:\\s*#(?:0[0-9a-f]f[0-9a-f]ff|00ffff|22d3ee|06b6d4|0891b2)\\b"
    why: "Bright cyan/electric-teal text on dark backgrounds is the 2015 futuristic-dashboard trope — high chroma, low information density."
    rewrite: "Use a desaturated accent from tokens.colors.accent or a neutral color at reduced alpha."
  - id: no-rainbow-accents
    category: color
    pattern: "(?:text|bg|border)-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-500[^\"]*\"[^>]*(?:text|bg|border)-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-500"
    why: "Multiple saturated Tailwind 500-series accents on the same surface reads as 'I couldn't decide'. Commit to one accent."
    rewrite: "Use tokens.colors.accent for primary actions; reserve status colors for semantic contexts only."
  - id: no-bounce-easing
    category: motion
    pattern: "cubic-bezier\\(0\\.68,\\s*-0\\.55"
    why: "Overshoot/bounce easings feel juvenile on UI surfaces."
    rewrite: "Use tokens.motion.easing.standard for UI, emphatic only for hero-level moments."
  - id: no-slow-default
    category: motion
    pattern: "transition(?:-[a-z]+)?:\\s*(?:all|[a-z-]+)\\s+(?:[5-9]\\d{2}|\\d{4,})ms"
    why: "Transitions >= 500ms on UI interactions feel sluggish; users interpret them as jank."
    rewrite: "Keep UI transitions under 280ms (tokens.motion.duration.slow). Reserve 500ms+ for hero entrances only."
  - id: no-permanent-spinner
    category: motion
    pattern: "animation:\\s*spin\\s+\\d+(?:\\.\\d+)?s\\s+linear\\s+infinite"
    why: "A perpetual spinner (`animation: spin … infinite`) left in a mockup for 'loading state' always ships to prod."
    rewrite: "Use discrete loading/empty/error variants side-by-side, or replace with a skeleton."
  - id: no-missing-focus
    category: interaction
    pattern: "<button[^>]*class=\"[^\"]*(?:focus:outline-none|outline-none)[^\"]*\"(?![^>]*focus-visible:)"
    why: "Removing the focus outline without replacing it breaks keyboard accessibility. This is the AI's laziest shortcut."
    rewrite: "Either keep the default outline or replace with `focus-visible:outline-2 focus-visible:outline-offset-2`."
  - id: no-placeholder-names
    category: content
    pattern: "\\b(John|Jane)\\s+Doe\\b"
    why: "John/Jane Doe placeholder strings leak into shipped UI when the AI skips real sample data."
    rewrite: "Use sample data that reflects the actual product (real-looking names, real tasks, real copy)."
  - id: no-lorem-ipsum
    category: content
    pattern: "\\bLorem\\s+ipsum\\s+dolor\\s+sit\\s+amet\\b"
    why: "Lorem ipsum in a shipped mockup means no one thought about what the copy should actually say."
    rewrite: "Write real copy that reflects the feature. If the feature is too early for copy, render a single-word label instead."

inspiration:
  - vercel
---

# Vercel — design contract

> **Contract:** Vercel Geist aesthetic — black-on-white, high-contrast, mono-forward, minimal chrome. Feels like a developer IDE exploded into a marketing site. The frontmatter above is the source of truth for machine-readable tokens; the sections below explain the why and give agents copy-paste prompts for component generation.

## 1. Visual Theme

Pure neutral — `bg: #FFFFFF`, `text: #000000`, `border: #EAEAEA`. No brand colour carries the personality; the personality is the geometry. Geist Sans takes centre stage at large sizes with tight leading (`1.1`); Geist Mono sits next to product names, version numbers, and code. One blue accent (`#0070F3`) shows up only on the primary CTA, active nav state, and focus ring. Cards have small radii (`lg: 8px`), 1px borders, and the faintest shadow. Typography is doing the heavy lifting.

## 2. Color Palette

See `tokens.colors` in the frontmatter. Narrative rules:

- **Background** is pure `#FFFFFF`. `surface` `#FAFAFA` lifts elevated cards by a whisper — not a gradient, not a shadow, just a 2-point luminance step.
- **Text** is pure `#000`. Pair with `#666` (not declared — use `text` at 60% alpha) for secondary content.
- **Accent** `#0070F3` is Vercel blue. Used ONLY for primary CTA, active nav underline, and focus ring. Never as a decorative tint, never as a body-text colour, never in gradients.
- **Status colours** (`success #0070F3`, `warning #F5A623`, `danger #E00000`) are semantic. Note that `success` and `info` share Vercel's blue by convention — green is reserved for the Geist `success` icon chip only.
- **Border** `#EAEAEA` is the 1px rule everywhere. Cards, inputs, dividers — all one hairline.

## 3. Typography

See `tokens.typography` in the frontmatter.

- **Primary** (`Geist` / `Geist Variable`) is Vercel's custom grotesque. It is a single family used at all sizes — there is no serif display face. Weight 700 for hero (scale[7] / 64px+), 600 for H1 / H2, 500 for labels and buttons, 400 for body.
- **Mono** (`Geist Mono`) sits next to product names (`vercel@4.2.0`), version numbers, CLI commands (`vercel deploy`), file paths, and env var keys. Its presence signals "this is the thing you type".
- Scale is 12/14/16/18/24/32/48/64/96 — note the octave jumps at the top for marketing hero moments.
- Line-height collapses from `1.5` at body to `1.1` at display. Display type is tight and structural.

## 4. Component Stylings

Token overrides live under `components` in the frontmatter. Narrative rules:

- **Button.** `md` (6px) radius, `16px` horizontal, `8px` vertical, weight 500. Primary is solid `text` (black) on `bg` (white) or inverted `bg` on `text`. The blue accent button is reserved for the one CTA that matters. Secondary is `bg` background with a `border` outline; tertiary is text-only.
- **Card.** `lg` (8px) radius, `24px` padding, `sm` shadow. 1px `border` outline always — Vercel cards define themselves by outline, not by lift. Cards never nest.
- **Input.** `md` (6px) radius, 1px `border` outline, `12px` / `8px` padding. Focus state: border replaced with `accent` 2px ring, zero offset.

## 5. Layout

- 12-column grid at ≥ 1024px; 6-column at 640–1023px; single column below.
- Max content width **1200px** for marketing; **1600px** for dashboards; code-heavy surfaces (deployment logs) use full-bleed.
- Section padding uses `spacing[7]` (64px) and `spacing[8]` (96px) between marketing sections.
- Hero layouts are left-aligned (never centred) with a code block or terminal screenshot as the visual anchor.

## 6. Depth

- `shadow.sm` (`0 1px 2px rgba(0,0,0,0.06)`) is the only shadow used on the working surface.
- `shadow.md` for popovers, menus, and hovered cards.
- `shadow.lg` only for modals.
- Do not stack shadows; pick one elevation per surface.

## 7. Do's and Don'ts

**Do**
- Lean on type weight + size for hierarchy. Borders and shadows are accents, not structure.
- Use `mono` liberally for anything typeable (commands, paths, env vars, version strings).
- Write sample data that reflects deployment concepts (project names like `my-next-app`, commit hashes, environment names `production`, `preview`).
- Keep hero sections pared down — one headline, one subhead, one CTA, one visual.

**Don't**
- Use any of the `antiPatterns` in the frontmatter. `apt-tools design-lint` will catch them.
- Apply gradients to type, cards, or chrome. The aesthetic is solids + borders.
- Use Inter as the declared primary font. Geist ships free via Vercel — use it or a proper grotesque alternative.
- Use green as a UI colour outside the Geist success chip component.

## 8. Responsive

- **Narrow** (<640px): single column, full-bleed hero type, stacked navigation that collapses into a hamburger.
- **Medium** (640–1023px): 6-column grid, condensed headers, code-block-first hero.
- **Wide** (≥1024px): 12-column grid, persistent top nav at 80px height, generous side margins.
- Touch targets are **40×40px** minimum on narrow viewports.

## 9. Agent Prompt Guide

Copy-paste starters for `apt:mockup` and ad-hoc generation.

### Build a button

> Render a primary button using `tokens.colors.text` (black) on `tokens.colors.bg` (white). Radius from `components.button.radius`. Padding from `components.button.paddingX/paddingY`. Font from `tokens.typography.families.primary` (Geist), weight 500, at scale[1] (14px). Focus: 2px outline in `tokens.colors.accent`, 0 offset.

### Build a card

> Render a card on `tokens.colors.bg`, 1px `tokens.colors.border` outline, `components.card.radius` corners, `components.card.padding` inner padding, `tokens.shadow.sm` shadow. Headline in `primary` weight 600 at scale[4] (24px). Meta row in `mono` at scale[0] (12px) — for timestamps, IDs, commit hashes.

### Build a data table

> Render a dense data table: `tokens.typography.scale[1]` (14px) body size, `tokens.spacing[2]` (12px) row padding, `tokens.colors.border` horizontal row separators only, `tokens.colors.surface` hover state. ID/hash/version columns use `mono` left-aligned; numeric columns right-aligned with `primary`.

### Reject criteria

Before handing off to `apt:plan`, every generated mockup must pass:

- `apt-tools design-validate DESIGN.md` → exit 0
- `apt-tools design-lint mockups/*.html` → zero violations against `antiPatterns`
- Visual read-through against §7 Do's and Don'ts (black-on-white, Geist family, 1px borders, no gradients)
