# Expo — Marketing Site Design Spec

> Source: expo.dev (live DOM, computed styles), 2026-07-25 (desktop 1512px).
> Coverage: home. Dashboard: behind login (`needs-dashboard`).
> Extracted with `scripts/extract.js`; 1 of the loaded stylesheets was cross-origin
> and unread, so the rule-level lists below are near-complete rather than exhaustive.
> Reality check: the queue hint said "near-black, restrained monochrome dev brand" —
> accurate. What it misses is that every control is a full pill (9999px), and that the
> one moment of colour is a React-flavoured rainbow gradient used as an accent wash.

## 1. Visual Theme & Atmosphere

Expo is a calm, near-black developer brand that gets its confidence from restraint
rather than decoration. The canvas is `#111113`; content sits on barely-lighter
`#18191b` and `#212225` cards, so the page reads as a soft stack of dark planes with
no shadows anywhere — depth comes entirely from these one- or two-step surface shifts
and `1px #2e3135` hairlines. Type is Inter at large display sizes with unusually tight
tracking (-3px at 64px), which gives headlines a dense, engineered look. Everything
else is quiet: mono eyebrows in uppercase JetBrains Mono, muted grey body copy, and
fully-rounded pill controls at a compact 36px height. Colour is rationed — a single
sky blue `#6abaff` for links, a green `#30a46c` status dot, and one React-style
rainbow gradient reserved for AI/SDK moments. The layout below the hero is a bento
grid of large-radius cards holding device mockups. Feels: dark, engineered, calm, precise.

## 2. Color Palette & Roles

### Text

| Token | Hex | Role / usage |
|---|---|---|
| text-primary | `#edeef0` | headings and body — the dominant text colour |
| text-secondary | `#b0b4ba` | lede paragraphs, muted supporting copy |
| text-tertiary | `#777b84` | captions, eyebrow labels, footer meta |

### Surfaces & borders

| Token | Hex | Role / usage |
|---|---|---|
| canvas | `#111113` | page background (set on `html`) |
| surface | `#18191b` | section bands and bento cards — the main raised plane |
| surface-raised | `#212225` | nested chips, inline code, inset controls |
| canvas-deep | `#0c0d0e` | deepest wells, device-mockup interiors |
| border | `#2e3135` | default hairline, card edges |
| border-strong | `#363a3f` | control borders, dividers (also seen at `0.5px`) |

### Accent & semantic

| Token | Hex | Role / usage |
|---|---|---|
| button-primary | `#ffffff` | primary CTA fill — white on near-black is the signature control |
| button-primary-hover | `#cccccc` | the only hover fill in the system |
| link | `#6abaff` | inline links |
| success | `#30a46c` | status dots ("What's new?" pip) |
| success-deep | `#28684a` | success backgrounds |

**Rules:** the accent budget is tiny — blue is for links only, green only for status.
The primary CTA is *white*, not a brand colour; treat white as the accent. Never place
a saturated fill on a large surface: colour appears as small dots, thin links, or the
rainbow gradient, never as a section background.

## 3. Typography Rules

`Inter` for everything (1081 elements), `JetBrains Mono` for code and eyebrow labels.
Preview substitutes Inter from Google Fonts (matching the real face) and JetBrains Mono.

| Role | Size/Line | Weight | Tracking |
|---|---|---|---|
| Display (h1/h2) | 64 / 70.4 | 600 | -3px (-4.7%) |
| Section (h3) | 48 / 52.8 | 600 | -2px (-4.2%) |
| Lede / body-large | 24 / 28.8 | 500 | -0.5px |
| Nav link | 14 / 19.6 | 400 | normal |
| Button label | 14 | 500 | normal |
| Mono eyebrow | size not captured | — | uppercase JetBrains Mono, `#777b84` |

Pattern: **weight ceiling is 600** — nothing is 700 or heavier. Tracking tightens
aggressively as size grows (-3px at 64px is the signature), and returns to normal at
UI sizes. Only the mono eyebrow is uppercase; headings are sentence case.

## 4. Component Stylings

### Buttons

- **Primary**: bg `#ffffff`, text `#111113`, radius `36px` (full pill), height `36px`, padding `0 16px`, Inter 14px/500.
- **Secondary**: dark fill on the surface step, same pill geometry and height.
- **Ghost / nav**: transparent, text `#edeef0`, radius `24px`, height `36px`, padding `0 16px`, weight 400.

### States

| Component | Rest | Hover | Focus | Disabled |
|---|---|---|---|---|
| Primary CTA | bg `#ffffff`, text `#111113`, pill, h36 | bg → `#cccccc` | **same as hover** — bg → `#cccccc` | not captured |
| Field | dark, `1px #2e3135` | — | — | not captured |

Notable system decision: Expo defines a custom **`hocus:` variant that fires on
`:hover` AND `:focus-visible` together**, so keyboard focus gets exactly the same
visual treatment as mouse hover. There is no separate focus ring. Reproduce this —
styling focus differently from hover would be off-system.

### Cards / bento

- `#18191b` fill, radius `40px` on the large bento tiles, `12px`/`16px` on smaller ones, `1px #2e3135` border, **no shadow**.

### Inline code

- `#212225` chip, JetBrains Mono, small radius — used mid-sentence inside headlines (e.g. one `install` away).

### Nav

- Wordmark left; Docs / Product / Solutions / Enterprise / Pricing / Blog at 14px/400; right side shows a GitHub star count, ghost "Log in", white pill "Sign up".

## 5. Layout Principles

Centred single column for the hero (headline, lede, two pill CTAs stacked centrally),
then a **bento grid** of unequal cards — one tall left tile beside a 2×2 cluster —
holding device mockups and short statements. Radii scale with tile size (40px on the
big tiles). 6 container queries are in use, so several components size against their
own box rather than the viewport. Generous vertical whitespace between bands; the
mono eyebrow acts as the band separator.

## 6. Depth & Elevation

Radius scale, by frequency: `9999px` (58 — pills dominate), `12px` (42), `16px` (18),
`40px` (12 — bento tiles), `36px` (6 — buttons), `25%` (avatars).

**Shadows: none.** The only box-shadow found across ~4,500 elements is Tailwind's
transparent ring reset (`rgba(0,0,0,0) 0 0 0 0`). Elevation is expressed purely as
surface steps (`#111113` → `#18191b` → `#212225`) plus `1px #2e3135` hairlines.
Do not add shadows — it is the clearest way to break this system.

Borders seen: `1px #2e3135` (most common), `1px #363a3f`, `0.5px #363a3f`, `2px #111113`.

## 7. Motion

| Property | Duration | Easing | Applies to |
|---|---|---|---|
| color, background-color, border-color | 0.15s | `cubic-bezier(.4,0,.2,1)` | buttons, links, cards (house transition, 67 elements) |
| opacity | 0.15s | `cubic-bezier(.4,0,.2,1)` | fades, reveals |
| color, background-color, border-color | 0.3s | `cubic-bezier(.4,0,.2,1)` | slower variant on larger surfaces |
| transform, translate, scale, rotate | 0.15s | `cubic-bezier(.4,0,.2,1)` | small interactive lifts |

House transition is **0.15s** on the Tailwind standard curve — fast and unshowy.
Custom easings also present: `cubic-bezier(.2,.5,.2,1)`, `cubic-bezier(.05,.7,.1,1)`,
`cubic-bezier(.32,.72,0,1)`.

Keyframes (18): `arc-sweep`, `ai-glow`, `logo-carousel`, `status-pulse`, `shimmer-text`,
`rainbow`, `progressFill`, `route-progress-trickle`, `searchUIBarLoader`,
`onboarding-progress`, `fade`, `fadeIn`, `fadeOut`, `slideDownAndFade`,
`slideRightAndFade`, `spin`, `ping`, `pulse`.

Actually applied on the page: `arc-sweep` 16s infinite (×6 — the ambient card sweeps),
`ai-glow` 11s infinite, `logo-carousel` 30s infinite (the trusted-by rail),
`status-pulse` 2.2s infinite (the green "What's new?" pip). All ambient animation is
slow (11–30s) and infinite; interaction motion is 0.15s. **2 `prefers-reduced-motion`
blocks are present** — the system honours it.

## 8. Backgrounds, Effects & WebGL

Flat dark surfaces, no grain or noise. Gradients are used sparingly as washes behind
AI/SDK moments:

- React-style rainbow: `linear-gradient(to right bottom in oklab, #e44857 0%, #c711e1 50%, …)` — note the `oklab` interpolation.
- Warm-to-cool: `linear-gradient(135.58deg, #f76809 3.36%, #2788f3 79.14%)`.
- Light-tint sheens on device mockups: `linear-gradient(135.58deg, #e4e1e6, rgba(228,225,230,.3))`.

No WebGL or Three.js observed. Device mockups are static imagery, not canvas.

## 9. Do's and Don'ts

### Do
- Build depth from surface steps and 1px hairlines, never shadows.
- Make every control a full pill at 36px height.
- Keep display type at weight 600 with heavy negative tracking (-3px at 64px).
- Use white as the primary-CTA fill on the near-black canvas.
- Give `:focus-visible` the identical treatment to `:hover` (the `hocus:` pattern).
- Set eyebrow labels in uppercase JetBrains Mono at `#777b84`.

### Don't
- Don't add box-shadows — the real site has none.
- Don't use weight 700+; 600 is the ceiling.
- Don't colour large surfaces; colour is dots, links, and one gradient wash.
- Don't use square or small-radius buttons — pills are the system.
- Don't introduce a second link colour beside `#6abaff`.

## 10. Responsive Behavior

23 breakpoints are declared — an unusually fine-grained ladder, mostly bespoke rather
than framework defaults.

| Breakpoint | What changes |
|---|---|
| 300, 340, 375, 400 | small-phone tiers — hero type scale-down |
| 468, 470, 471, 480, 500, 550, 600 | dense band of bespoke tweaks for bento tile wrapping |
| 640, 1024 | Tailwind defaults — column collapse, nav → menu |
| max 767, 787, max 788, 788 | the mobile/desktop nav switch, handled from both sides |
| 900, 1008, 1159, 1160, 1328, 1572 | large-desktop bento reflow and max-width framing |

Also: **6 container queries** (components size to their own box, not the viewport),
1 `prefers-color-scheme: dark` block, and 2 `prefers-reduced-motion` blocks. The site
is dark by default rather than theme-switched.

## 11. Agent Prompt Guide

- Canvas `#111113` · Surface `#18191b` · Text `#edeef0` · Muted `#b0b4ba` · Border `#2e3135` · Link `#6abaff`
- Font: Inter (JetBrains Mono for code/eyebrows) · Display 64/600 at -3px tracking · Body 24/500
- Radius: pill 9999px controls / 40px bento tiles · Control height: 36px
- Focus: identical to hover (bg `#ffffff` → `#cccccc`) · Shadows: none

<expo_design_language>
A near-black React-Native developer brand: canvas #111113, cards stepping up through
#18191b to #212225, hairlines #2e3135, text #edeef0 over muted #b0b4ba. Inter
throughout with a hard weight ceiling of 600 and aggressive negative tracking (-3px at
64px display, -2px at 48px); JetBrains Mono for uppercase eyebrow labels and inline
code chips. Every control is a full pill at 36px height, 0 16px padding, 14px/500 —
the primary CTA is white #ffffff with text #111113, hovering to #cccccc, and
focus-visible is styled identically to hover. Absolutely no shadows: depth is surface
steps plus 1px borders. Layout is a centred hero over a bento grid of 40px-radius
tiles. Transitions are 0.15s cubic-bezier(.4,0,.2,1); ambient animations run 11–30s
infinite and reduced-motion is honoured. Colour is rationed to a #6abaff link, a
#30a46c status dot, and one oklab rainbow gradient wash.
Feels: dark, engineered, calm, precise.
Avoid: box-shadows of any kind, weight 700, square or small-radius buttons, coloured
section backgrounds, and a focus ring that differs from hover.
</expo_design_language>
