Dovetail
MIT
A dark-first React component system with precise spacing and restrained typography, built for product teams prioritising clarity and developer efficiency
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install dovetail# layout.md — Dovetail Design System
---
## 0. Quick Reference
**Stack:** React + Emotion (CSS-in-JS, Chakra/Emotion class patterns). No native CSS custom properties — all tokens reconstructed from computed styles. Use as `var(--token-name)` in CSS, `style={{ prop: 'var(--token-name)' }}` in JSX, or `bg-[var(--token-name)]` in Tailwind.
```css
:root {
/* Colour */
--color-bg-dark: #0a0a0a; /* Primary dark surface */
--color-bg-hover-dark: #313131; /* Hover state on dark surface */
--color-bg-hover-light: #d8d8d8; /* Hover state on light surface */
--color-text-primary: #ffffff; /* Primary text on dark bg */
--color-text-on-light: #000000; /* Text on white/light bg */
--color-text-muted: rgba(255,255,255,0.64); /* Secondary/muted text */
--color-surface-light: #ffffff; /* Light card/panel surface */
--color-border-subtle: rgba(255,255,255,0.24); /* Subtle border */
/* Typography */
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica", "Arial", "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", "SF Mono", Consolas, Roboto, sans-serif;
--font-serif: "EB Garamond", Georgia, serif;
/* Spacing */
--dovetail-space-xs: 24px;
--dovetail-space-sm: 32px;
--dovetail-space-md: 48px;
--dovetail-space-lg: 64px;
--dovetail-space-xl: 80px;
--dovetail-space-2xl: 124px;
/* Radius */
--dovetail-radius-sm: 4px;
--dovetail-radius-md: 8px;
/* Motion */
--dovetail-duration-fast: 0.15s;
--dovetail-duration-base: 0.2s;
--dovetail-duration-slow: 0.3s;
--dovetail-ease-default: ease;
}
```
```tsx
// Primary CTA Button — production-ready
export const PrimaryButton = ({ children, disabled }: { children: React.ReactNode; disabled?: boolean }) => (
<button
disabled={disabled}
style={{
fontFamily: 'var(--font-sans)', fontSize: '20px', fontWeight: 600,
backgroundColor: 'var(--color-surface-light)', color: 'var(--color-text-on-light)',
borderRadius: 'var(--dovetail-radius-sm)', padding: '8px 16px',
border: 'none', cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1, transition: `all var(--dovetail-duration-slow) var(--dovetail-ease-default)`,
}}
>{children}</button>
);
```
**NEVER** use hardcoded hex colours — always reference a `--dovetail-*` or `--color-*` token.
**NEVER** apply `border-radius` > `8px` on buttons or cards (no pill shapes in this system).
**NEVER** use Inter for button labels — CTA labels use `--font-mono` (JetBrains Mono), uppercase, `letter-spacing: 1px`.
**NEVER** set `font-size` below `12px` — `--dovetail-font-size-xs` is the floor.
**NEVER** use warm colours (orange, yellow, red-adjacent) — palette is strictly achromatic.
**NEVER** skip hover/focus/disabled states on interactive elements — all three are required.
> Full design system → see **layout.md**
---
## 1. Design Direction & Philosophy
### Character & Mood
Dovetail is a **research intelligence platform** for product teams. The aesthetic is **dark, minimal, typographically serious** — an editorial authority rather than a cheerful SaaS product. The visual language communicates rigour, depth, and trust. The palette is near-monochrome: nearly all surfaces are black or white with very little chromatic colour.
### Typographic Identity
Three fonts coexist with distinct roles:
- **Inter** — the workhorse: navigation, body, headings, UI text
- **JetBrains Mono** — buttons and technical labels only; uppercase with tracked letter-spacing signals precision and code-adjacent intent
- **EB Garamond** — editorial accent for pull quotes, testimonials, or brand storytelling moments; italic weight is available
### What This Design Explicitly Rejects
- **No warm accent colours.** There are no oranges, yellows, greens, blues, or brand accent hues visible in the computed styles. The system is achromatic.
- **No pill/rounded buttons.** Maximum border-radius is `8px`. No `border-radius: 50px` or `9999px`.
- **No decorative gradients** on typographic elements. Headings are solid white on dark.
- **No default Inter weight 400 for CTAs.** Buttons use a monospaced font; Inter-400 is body copy.
- **No aggressive shadow systems.** The design is flat — elevation is communicated through surface colour contrast, not box-shadow stacks.
- **No playfulness.** No rounded UI, illustrated characters, bright colours, or friendly micro-copy registers.
---
## 2. Colour System
### Tier 1 — Primitive Values
```css
/* ── Primitive Palette (reconstructed from computed styles) ── */
:root {
--primitive-black: #000000; /* Pure black — text on light bg */
--primitive-near-black: #0a0a0a; /* Near-black — primary page bg */
--primitive-dark-800: #313131; /* Dark grey — hover state on dark surfaces */
--primitive-dark-600: #3b3b3b; /* Mid-dark grey — hover on light/inverted surfaces */
--primitive-light-200: #cecece; /* Light grey — hover state on light buttons */
--primitive-light-100: #d8d8d8; /* Near-white grey — hover on light surfaces (dark theme) */
--primitive-white: #ffffff; /* Pure white — text and light surfaces */
--primitive-near-white: #fafafa; /* Off-white — focus outline in light contexts */
--primitive-muted-white: rgba(255, 255, 255, 0.64); /* 64% white — muted/secondary text */
--primitive-border-white: rgba(255, 255, 255, 0.24); /* 24% white — subtle borders */
--primitive-mid-grey: #a7a7a7; /* Ghost/disabled text in light context */
--primitive-mid-grey-2: #585858; /* Dimmed icon/muted text hover */
}
```
### Tier 2 — Semantic Aliases
```css
/* ── Semantic Tokens (reconstructed — moderate confidence, inferred from state styles) ── */
:root {
/* Surfaces */
--color-bg-page: var(--primitive-near-black); /* Primary page background */
--color-bg-surface: var(--primitive-near-black); /* Card/panel default bg (dark theme) */
--color-bg-surface-light: var(--primitive-white); /* Card/panel default bg (light theme) */
--color-bg-hover-dark: var(--primitive-dark-800); /* Hover bg on dark surfaces */
--color-bg-hover-light: var(--primitive-light-100); /* Hover bg on light surfaces (dark context) */
--color-bg-hover-inverted: var(--primitive-dark-600);/* Hover bg in light-theme context */
/* Text */
--color-text-primary: var(--primitive-white); /* Primary text on dark bg */
--color-text-on-light: var(--primitive-black); /* Text on white/light surfaces */
--color-text-muted: var(--primitive-muted-white); /* Secondary / supporting text */
--color-text-ghost: var(--primitive-mid-grey); /* Ghost/disabled text */
--color-text-hover: var(--primitive-white); /* Text colour on hover (dark context) */
/* Borders */
--color-border-subtle: var(--primitive-border-white);/* Subtle dividers on dark bg */
--color-border-hover: var(--primitive-border-white); /* Button borders on hover */
/* Focus */
--color-focus-ring-dark: var(--primitive-white); /* Focus outline on dark bg */
--color-focus-ring-light: var(--primitive-near-black);/* Focus outline on light bg */
}
```
### Tier 3 — Component Tokens
```css
/* ── Component Colour Tokens (reconstructed) ── */
:root {
/* Buttons — primary (white on dark page) */
--btn-primary-bg: var(--color-bg-surface-light);
--btn-primary-text: var(--color-text-on-light);
--btn-primary-bg-hover: var(--primitive-light-200); /* rgb(206,206,206) */
--btn-primary-focus-ring: var(--color-focus-ring-light);
/* Buttons — secondary/ghost (dark on dark page) */
--btn-ghost-bg: transparent;
--btn-ghost-text: var(--color-text-primary);
--btn-ghost-bg-hover: var(--color-bg-hover-dark); /* rgb(49,49,49) */
--btn-ghost-border: var(--color-border-subtle);
--btn-ghost-focus-ring: var(--color-focus-ring-dark);
/* Buttons — disabled (all variants) */
--btn-disabled-opacity: 0.6;
/* Navigation */
--nav-bg: transparent;
--nav-text: var(--color-text-primary);
--nav-text-hover: var(--color-text-primary);
/* Scroll theme variants — Dovetail uses html[data-scroll-theme] attribute */
/* dark theme: hover bg = #313131 */
/* light theme: hover bg = #d8d8d8 (dark buttons), #3b3b3b (light buttons) */
}
```
### Scroll-Theme System
Dovetail uses a `data-scroll-theme="dark|light"` attribute on `<html>` to swap surface contexts as the user scrolls. Components must handle BOTH variants.
| Context | Button bg hover | Focus ring |
|---|---|---|
| `data-scroll-theme="dark"` | `#313131` | `#ffffff` |
| `data-scroll-theme="light"` | `#d8d8d8` / `#3b3b3b` (inverted) | `#0a0a0a` / `#fafafa` |
---
## 3. Typography System
**Three-font system. Inter = UI & body. JetBrains Mono = buttons & labels. EB Garamond = editorial.**
```css
/* ── Font Stacks ── */
:root {
--font-sans: "Inter", -apple-system, system-ui, BlinkMacSystemFont, "Helvetica", "Arial", "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", "SF Mono", Consolas, Roboto, sans-serif;
--font-serif: "EB Garamond", Georgia, serif; /* editorial accent only */
}
```
### Composite Typography Tokens
```css
/* ── Heading Display — h1 / h2 ── */
.type-display {
font-family: var(--font-sans);
font-size: 56px; /* --dovetail-font-size-3xl */
font-weight: 500; /* --dovetail-font-weight-medium */
line-height: 64px; /* extracted: high confidence */
letter-spacing: -2px; /* extracted: high confidence */
color: var(--color-text-primary);
}
/* ── Heading Large — h3 section heads ── */
.type-heading-lg {
font-family: var(--font-sans);
font-size: 40px; /* --dovetail-font-size-2xl */
font-weight: 500;
line-height: 48px; /* reconstructed: moderate confidence, interpolated */
letter-spacing: -1px; /* reconstructed: moderate confidence */
color: var(--color-text-primary);
}
/* ── Heading Medium — h3 card/feature heads ── */
.type-heading-md {
font-family: var(--font-sans);
font-size: 24px; /* --dovetail-font-size-xl */
font-weight: 500;
line-height: 32px; /* extracted: high confidence */
letter-spacing: -0.5px; /* extracted: high confidence */
color: var(--color-text-primary);
}
/* ── Body Large — primary body copy ── */
.type-body-lg {
font-family: var(--font-sans);
font-size: 20px; /* --dovetail-font-size-lg */
font-weight: 400;
line-height: 28px; /* extracted: high confidence */
letter-spacing: normal;
color: var(--color-text-primary);
}
/* ── Body Medium ── */
.type-body-md {
font-family: var(--font-sans);
font-size: 16px; /* --dovetail-font-size-md */
font-weight: 400;
line-height: 24px; /* reconstructed: moderate confidence */
letter-spacing: normal;
color: var(--color-text-primary);
}
/* ── Body Small ── */
.type-body-sm {
font-family: var(--font-sans);
font-size: 14px; /* --dovetail-font-size-sm */
font-weight: 400;
line-height: 19.6px; /* --dovetail-line-height-normal, extracted */
letter-spacing: normal;
color: var(--color-text-muted);
}
/* ── Label / Caption ── */
.type-label {
font-family: var(--font-sans);
font-size: 12px; /* --dovetail-font-size-xs */
font-weight: 400;
line-height: 18px; /* --dovetail-line-height-tight, extracted */
letter-spacing: normal;
color: var(--color-text-muted);
}
/* ── Button Label — monospaced, uppercase ── */
.type-button {
font-family: var(--font-mono);
font-size: 12px; /* extracted: high confidence */
font-weight: 400;
line-height: 12px; /* extracted: high confidence */
letter-spacing: 1px; /* extracted: high confidence */
text-transform: uppercase;
color: var(--color-text-muted); /* rgba(255,255,255,0.64) on dark bg */
}
/* ── Nav Link ── */
.type-nav {
font-family: var(--font-sans);
font-size: 20px;
font-weight: 400;
line-height: normal;
letter-spacing: normal;
color: var(--color-text-primary);
}
/* ── CTA Link (white pill on page, e.g. "Get started") ── */
.type-cta-link {
font-family: var(--font-sans);
font-size: 20px;
font-weight: 600; /* --dovetail-font-weight-semibold */
line-height: normal;
letter-spacing: normal;
color: var(--color-text-on-light);
}
```
### Typographic Rules
- **h1 and h2 are visually identical** at 56px/500/−2px letter-spacing. Section differentiation is done by content and position, not by changing the heading style.
- **Body text at 20px** is large by industry norms — this is intentional. It signals editorial confidence.
- NEVER use EB Garamond for UI controls, labels, or navigation. It is strictly for editorial/testimonial moments.
- NEVER use letter-spacing values other than those documented. Tracking is tight-to-negative on headings, 1px tracked on mono button labels, and `normal` on body.
---
## 4. Spacing & Layout
**Base unit: 8px.** The spacing scale starts at 24px (3× base) — micro spacing below 24px appears only as internal component padding (e.g. `8px 16px` on link/button padding).
```css
/* ── Spacing Scale ── */
:root {
/* Internal component spacing (not in the named scale) */
--space-component-xs: 8px; /* Button/link internal padding (vertical) */
--space-component-sm: 16px; /* Button/link internal padding (horizontal) */
/* Section / layout spacing scale */
--dovetail-space-xs: 24px; /* Tight section sub-gap, nav padding */
--dovetail-space-sm: 32px; /* Component internal gap, card padding */
--dovetail-space-md: 48px; /* Section internal gap */
--dovetail-space-lg: 64px; /* Between major section blocks */
--dovetail-space-xl: 80px; /* Large section vertical rhythm */
--dovetail-space-2xl: 124px; /* Hero / XL section vertical padding */
}
/* ── Border Radius ── */
:root {
--dovetail-radius-sm: 4px; /* Skip link, small UI elements */
--dovetail-radius-md: 8px; /* Buttons (28 elements), nav dropdowns, cards */
}
/* ── Container ── */
:root {
--container-max: 1512px; /* Widest breakpoint from media queries */
--container-content: 1280px; /* Standard content max-width */
--container-narrow: 1185px; /* Narrow content column */
}
/* ── Grid System ── */
:root {
--grid-columns: 12;
--grid-gutter: var(--dovetail-space-sm); /* 32px — reconstructed: moderate confidence */
}
```
### Breakpoint Scale
| Token | Value | Description |
|---|---|---|
| `--bp-sm` | 799px | Mobile → tablet transition |
| `--bp-md` | 850px | Small tablet |
| `--bp-lg` | 990px | Tablet → desktop |
| `--bp-xl` | 1185px | Narrow desktop |
| `--bp-2xl` | 1280px | Standard desktop |
| `--bp-3xl` | 1512px | Wide / large desktop |
### Layout Decision Rules
- **Use `display: flex; flex-direction: row`** for navigation, button groups, and horizontal card grids.
- **Navigation:** `justify-content: space-between; align-items: center; padding: 24px` — anchored to extracted role_navigation styles.
- **Section containers:** max-width capped at `var(--container-content)` (1280px), centred with `margin: 0 auto`.
- NEVER use `position: absolute` to achieve horizontal layout — use flexbox.
---
## 5. Page Structure & Layout Patterns
> **Note:** No page screenshots available. All section ordering is inferred from the component inventory, layout digest, and computed styles. Rows marked **(inferred)** are not visually confirmed.
### 5.1 Section Map
| Order | Section | Layout Type | Approx Height | Key Elements |
|---|---|---|---|---|
| 1 | Navigation / Header | Flex row, full-width | ~72px | Logo (left), nav links (centre), CTA button (right) |
| 2 | Hero | Full-width, dark bg | ~600–800px (inferred) | H1 56px, subheading body-lg 20px, primary CTA link-button |
| 3 | Social Proof / Ratings | Flex row (inferred) | ~80px (inferred) | G2 / Capterra ratings at 12px label font |
| 4 | Feature Overview | 2-col or 3-col grid (inferred) | ~480px (inferred) | H3 24px card heads, body-sm text, feature icons |
| 5 | ROI / Stats Strip | Full-width, dark bg (inferred) | ~320px (inferred) | 40px stat numerals, 14px supporting text |
| 6 | Product Deep-Dive | Alternating 2-col (inferred) | ~600px (inferred) | H2 56px section head, body-lg, product UI screenshots |
| 7 | AI Infrastructure / Trust | Dark section (inferred) | ~480px (inferred) | H2 56px, body-lg, feature list cards |
| 8 | Customer Testimonials | Card grid (inferred) | ~400px (inferred) | EB Garamond italic quotes, role/name labels 14px |
| 9 | CTA / Sign-up Banner | Full-width, centred (inferred) | ~300px (inferred) | H2 40px, white primary button |
| 10 | Footer | Multi-col flex (inferred) | ~200px (inferred) | Nav links 14px, legal text 12px |
### 5.2 Layout Patterns
**Navigation (extracted):**
```
display: flex | flex-direction: row | justify-content: space-between | align-items: center | padding: 24px
```
- Logo pinned left, CTA button pinned right, nav links in the centre or right cluster.
- Background: transparent (overlaid on hero), transitions to opaque on scroll (0.3s ease).
**Hero (inferred from h1 styles):**
- Full-width dark (`#0a0a0a`) background.
- H1 at 56px/500/−2px letter-spacing, white, left-aligned (extracted `text-align: left`).
- Body copy at 20px/400/28px line-height below the H1.
- CTA: white `background-color: #ffffff` link-button, `border-radius: 4px`, `padding: 8px 16px`, `font-weight: 600`.
**Feature Cards (inferred from h3 census — 28 button elements at radius-md):**
- Cards use `border-radius: 8px` (--dovetail-radius-md), dark surface.
- Grid: likely 3 columns at desktop, collapsing to 1 at ≤799px.
- Card head: 24px/500/−0.5px. Body: 14px/400. Margin-bottom on h3: 16px.
**Stats Strip (inferred from 40px font-size elements):**
- Numerals at 40px/500, supporting labels at 14px/400.
- Full-width, likely flex row with equal-width columns.
### 5.3 Visual Hierarchy
1. **H1 at 56px/−2px tracking** is the dominant element — nothing competes in size.
2. **White CTA button** (`background: #ffffff`, `color: #000000`) is the only high-contrast achromatic affordance on the dark page — it reads as the primary action.
3. Section headings (h2 at 56px) re-anchor hierarchy per section — the page uses repeated large-heading rhythm.
4. Body copy at **20px** (larger than industry standard 16px) creates a generous reading rhythm.
5. **Rating labels** (12px monospaced style, muted white) are the lowest hierarchy level.
6. Whitespace between sections is substantial: `--dovetail-space-xl` (80px) to `--dovetail-space-2xl` (124px).
### 5.4 Content Patterns
**Repeating pattern — Feature Section:**
```
[Section label — 12px monospaced, uppercase, muted]
[H2 or H3 — 56px or 40px, white, weight 500]
[Body paragraph — 20px, 400, white]
[CTA link or button]
[Visual asset / product screenshot — right or below]
```
**Repeating pattern — Card Grid:**
```
[Grid of 3 cards]
└── [H3 — 24px/500/−0.5px]
└── [Body — 14–16px/400]
└── [Optional: icon or label]
```
**CTA colour anchor:** Primary CTA button is **white background (`#ffffff`)** with **black text (`#000000`)** — the only light element on dark pages. Secondary/ghost buttons are transparent with white text and a subtle white border (`rgba(255,255,255,0.24)`).
---
## 6. Component Patterns
### 6.1 Primary Button (White CTA)
**Anatomy:** `<button>` → `<span>` (label text, optional) → optional trailing arrow icon
**Token mappings:**
| State | Background | Text colour | Border | Opacity | Transform |
|---|---|---|---|---|---|
| Default | `#ffffff` | `#000000` | none | 1 | none |
| Hover | `#cecece` | `#000000` | none | 1 | none |
| Focus-visible | `#ffffff` | `#000000` | outline `1px solid #0a0a0a`, offset 1px | 1 | none |
| Active | `#cecece` | `#000000` | none | 1 | none |
| Disabled | `#ffffff` | `#000000` | none | 0.6 | none |
```tsx
import React from 'react';
interface PrimaryButtonProps {
children: React.ReactNode;
disabled?: boolean;
onClick?: () => void;
type?: 'button' | 'submit' | 'reset';
}
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
children,
disabled = false,
onClick,
type = 'button',
}) => {
const [isHovered, setIsHovered] = React.useState(false);
return (
<button
type={type}
disabled={disabled}
onClick={onClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
fontFamily: 'var(--font-sans)',
fontSize: '20px',
fontWeight: 600,
lineHeight: 'normal',
letterSpacing: 'normal',
backgroundColor: isHovered ? '#cecece' : 'var(--color-surface-light)',
color: 'var(--color-text-on-light)',
borderRadius: 'var(--dovetail-radius-sm)', /* 4px */
padding: '8px 16px',
border: 'none',
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1,
pointerEvents: disabled ? 'none' : 'auto',
transition: `all var(--dovetail-duration-slow) var(--dovetail-ease-default)`,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
textDecoration: 'none',
}}
>
{children}
</button>
);
};
```
---
### 6.2 Ghost / Secondary Button (Dark Surface)
**Anatomy:** `<button>` → text label
| State | Background | Text | Border | Opacity |
|---|---|---|---|---|
| Default | transparent | `#ffffff` | `rgba(255,255,255,0.24)` | 1 |
| Hover | `#313131` | `#ffffff` | `rgba(255,255,255,0.24)` | 1 |
| Focus-visible | `#313131` | `#ffffff` | outline `1px solid #ffffff`, offset 1px | 1 |
| Disabled | transparent | `#ffffff` | `rgba(255,255,255,0.24)` | 0.6 |
```tsx
export const GhostButton: React.FC<PrimaryButtonProps> = ({ children, disabled, onClick }) => (
<button
disabled={disabled}
onClick={onClick}
style={{
fontFamily: 'var(--font-sans)',
fontSize: '20px',
fontWeight: 400,
backgroundColor: 'transparent',
color: 'var(--color-text-primary)',
borderRadius: 'var(--dovetail-radius-md)', /* 8px */
padding: '8px 16px',
border: '1px solid var(--color-border-subtle)',
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1,
pointerEvents: disabled ? 'none' : 'auto',
transition: `all var(--dovetail-duration-slow) var(--dovetail-ease-default)`,
}}
onMouseEnter={e => { (e.currentTarget as HTMLButtonElement).style.backgroundColor = '#313131'; }}
onMouseLeave={e => { (e.currentTarget as HTMLButtonElement).style.backgroundColor = 'transparent'; }}
>
{children}
</button>
);
```
---
### 6.3 Navigation Item
**Anatomy:** `<nav>` (role="navigation") → flex container → `<a>` nav links + CTA button
| State | Text colour | Background |
|---|---|---|
| Default | `#ffffff` | transparent |
| Hover | `#ffffff` | transparent (no bg change on links) |
| Focus-visible | `#ffffff` | outline `1px solid #ffffff` |
```tsx
export const NavBar: React.FC<{ items: { label: string; href: string }[] }> = ({ items }) => (
<nav
role="navigation"
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 'var(--dovetail-space-xs)', /* 24px */
backgroundColor: 'transparent',
transition: `all var(--dovetail-duration-slow) var(--dovetail-ease-default)`,
fontFamily: 'var(--font-sans)',
fontSize: '20px',
fontWeight: 400,
color: 'var(--color-text-primary)',
}}
>
<a href="/" style={{ color: 'inherit', textDecoration: 'none' }}>Dovetail</a>
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
{items.map(item => (
<a
key={item.href}
href={item.href}
style={{
color: 'var(--color-text-primary)',
textDecoration: 'none',
transition: `color var(--dovetail-duration-fast) var(--dovetail-ease-default)`,
}}
>
{item.label}
</a>
))}
</div>
<PrimaryButton>Get started</PrimaryButton>
</nav>
);
```
---
### 6.4 Mono Label / Button Tag
Used for section category labels ("Product", "Use cases", monospaced uppercase tags).
**Anatomy:** `<span>` or `<p>` with monospaced uppercase styling
```tsx
export const MonoLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<span
style={{
fontFamily: 'var(--font-mono)',
fontSize: '12px',
fontWeight: 400,
lineHeight: '12px',
letterSpacing: '1px',
textTransform: 'uppercase',
color: 'var(--color-text-muted)', /* rgba(255,255,255,0.64) */
}}
>
{children}
</span>
);
```
---
### 6.5 Feature Card
**Anatomy:** `<article>` → heading (24px) → body text (14–16px)
```tsx
export const FeatureCard: React.FC<{
heading: string;
body: string;
icon?: React.ReactNode;
}> = ({ heading, body, icon }) => (
<article
style={{
backgroundColor: 'var(--color-bg-surface)',
borderRadius: 'var(--dovetail-radius-md)', /* 8px */
padding: 'var(--dovetail-space-sm)', /* 32px */
display: 'flex',
flexDirection: 'column',
gap: '16px',
transition: `background-color var(--dovetail-duration-slow) var(--dovetail-ease-default)`,
}}
>
{icon && <div style={{ marginBottom: '8px' }}>{icon}</div>}
<h3
style={{
fontFamily: 'var(--font-sans)',
fontSize: '24px',
fontWeight: 500,
lineHeight: '32px',
letterSpacing: '-0.5px',
color: 'var(--color-text-primary)',
margin: '0 0 16px 0',
}}
>
{heading}
</h3>
<p
style={{
fontFamily: 'var(--font-sans)',
fontSize: '14px',
fontWeight: 400,
lineHeight: '19.6px',
letterSpacing: 'normal',
color: 'var(--color-text-muted)',
margin: 0,
}}
>
{body}
</p>
</article>
);
```
---
## 7. Elevation & Depth
Dovetail uses a **flat design system**. Depth is achieved through surface colour contrast (dark vs. darker), not box shadows.
```css
/* ── Shadow Tokens (reconstructed — low confidence; no box-shadow values extracted) ── */
:root {
--shadow-none: none; /* Default state — all cards, buttons */
--shadow-overlay: none; /* Modals/dropdowns — no shadow found in extraction */
/* Borders as elevation substitute */
--border-subtle-dark: 1px solid rgba(255, 255, 255, 0.24); /* Cards on dark bg */
--border-focus: 1px solid #ffffff; /* Focus rings on dark bg */
--border-focus-light: 1px solid #0a0a0a; /* Focus rings on light bg */
}
/* ── Z-Index Scale (reconstructed — moderate confidence) ── */
:root {
--z-base: 0;
--z-raised: 10; /* Cards with hover state */
--z-dropdown: 100; /* Nav dropdowns, menus */
--z-sticky: 200; /* Sticky navigation */
--z-overlay: 300; /* Modals, drawers */
--z-toast: 400; /* Notifications */
}
```
**Key principle:** The nav uses `transition: 0.3s` suggesting it transitions from transparent (over hero) to an opaque dark state on scroll — this is the primary "elevation" moment in the design.
---
## 8. Motion
```css
/* ── Duration Tokens ── */
:root {
--dovetail-duration-fast: 0.15s; /* Links, colour changes (22 elements) */
--dovetail-duration-base: 0.2s; /* Buttons, hover states (18 elements) */
--dovetail-duration-slow: 0.3s; /* Navigation, cards, large transitions (28 elements) */
}
/* ── Easing ── */
:root {
--dovetail-ease-default: ease; /* Applied to 142 elements — universal default */
}
/* ── Named Transitions ── */
:root {
--transition-color: color var(--dovetail-duration-fast) var(--dovetail-ease-default);
--transition-bg: background-color var(--dovetail-duration-slow) var(--dovetail-ease-default);
--transition-all: all var(--dovetail-duration-slow) var(--dovetail-ease-default);
--transition-nav: all var(--dovetail-duration-slow) var(--dovetail-ease-default);
}
```
### Motion Rules
| Trigger | Property | Duration | Easing |
|---|---|---|---|
| Link hover | `color` | `0.15s` | `ease` |
| Button hover (bg) | `background-color` | `0.3s` | `ease` |
| Nav scroll theme change | `all` | `0.3s` | `ease` |
| Arrow icon nudge on hover | `transform: translate3d(4px, 0, 0)` | `0.2s` | `ease` |
| Disabled state | none (instant) | — | — |
| Focus ring appearance | `outline` | `0.15s` | `ease` |
### When NOT to Animate
- NEVER animate `font-size`, `font-weight`, or `layout` properties — these cause reflow.
- NEVER use motion for decorative purposes — all motion in this system is functional (state feedback).
- Respect `prefers-reduced-motion`: wrap `transition` and `transform` rules in a media query check.
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition: none !important;
animation: none !important;
}
}
```
---
## 9. Anti-Patterns & Constraints
1. **NEVER hardcode hex colours inline → Why it fails:** Emotion/CSS-in-JS components will scatter `#313131` and `rgba(255,255,255,0.64)` across dozens of files with no single source of truth. When scroll-theme variants need to change a hover colour, every file must be manually updated and the agent will inevitably miss instances. **What to do instead:** Always reference `var(--color-bg-hover-dark)` or the semantic token. Define the palette once in `:root`.
2. **NEVER use `border-radius` > 8px on interactive elements → Why it fails:** AI agents default to `border-radius: 9999px` (pill) for CTA buttons because pill buttons are common in SaaS. Dovetail uses sharp-to-subtle radius (`4px` on link-CTAs, `8px` on ghost buttons). A pill button breaks the design language entirely. **What to do instead:** Always use `var(--dovetail-radius-sm)` (4px) for primary CTAs and `var(--dovetail-radius-md)` (8px) for card elements.
3. **NEVER use Inter (or any sans font) for button label text → Why it fails:** AI agents default to the body font for all text. Dovetail's button labels use `JetBrains Mono`, uppercase, `letter-spacing: 1px` — this is a deliberate code-adjacent signal. Using Inter on buttons loses the typographic identity and makes buttons look like plain text links. **What to do instead:** Apply `var(--font-mono)` with `text-transform: uppercase; letter-spacing: 1px` for all button label text.
4. **NEVER construct Tailwind class names dynamically (e.g. `bg-${colour}`) → Why it fails:** Tailwind's JIT compiler only includes classes that appear as complete strings in source. Dynamic construction means the class is never included in the build and the style silently doesn't apply. **What to do instead:** Use `style={{ backgroundColor: 'var(--color-bg-hover-dark)' }}` for dynamic values, or maintain a full allowlist of static class names.
5. **NEVER omit the `data-scroll-theme` hover variant → Why it fails:** Buttons are used on both dark and light scroll sections. Implementing only the dark-theme hover (`#313131`) means buttons on light-theme sections get a near-invisible dark-on-dark hover state. **What to do instead:** Always implement both `html[data-scroll-theme="dark"]` and `html[data-scroll-theme="light"]` hover variants. Use CSS attribute selectors or a React context that reads the `data-scroll-theme` attribute.
6. **NEVER use spacing values outside the defined scale → Why it fails:** AI agents invent arbitrary values like `margin: 20px` or `gap: 10px` when no constraint is documented. These break the 8px-base rhythm. Dovetail's layout scale jumps from component-internal (`8px`, `16px`) directly to section-level (`24px`, `32px`, `48px`). There is no `20px` gap. **What to do instead:** Use only `--dovetail-space-*` tokens. For internal component padding use `8px` (vertical) and `16px` (horizontal) only.
7. **NEVER apply `!important` to override theme states → Why it fails:** The scroll-theme system uses attribute selectors (`html[data-scroll-theme]`) with high specificity. Adding `!important` to a component style creates a sealed override that breaks the entire scroll-theme colour swap system — dark-section buttons will stay their hover colour on light sections. **What to do instead:** Match or slightly exceed the specificity of the attribute selector using CSS nesting or a parent class. Never `!important`.
8. **NEVER use `position: absolute` to achieve horizontal layout → Why it fails:** Navigation and card grid layouts need to be responsive. Absolutely positioned elements fall out of document flow and overlap at smaller breakpoints. **What to do instead:** Use `display: flex; flex-direction: row` with `justify-content: space-between` for nav and `gap` for card grids.
9. **NEVER render placeholder/lorem-ipsum content in production components → Why it fails:** AI agents filling card components with "Lorem ipsum" text at 14px may accidentally set a `min-height` to accommodate it, causing layout collapse when real content is shorter. **What to do instead:** Design cards to be content-agnostic: no fixed heights, use `min-height` only if a grid requires alignment, and test with single-word and multi-paragraph content.
10. **NEVER use EB Garamond for UI elements → Why it fails:** Garamond at small sizes (below 18px) renders poorly on screen and reads as a design error in a product UI context. AI agents, seeing the font declared, may apply it to body copy or labels. **What to do instead:** EB Garamond is strictly for editorial moments (pull quotes, testimonials) at 24px+ sizes, `font-style: italic`. Never use it for nav, buttons, labels, or body paragraphs.
---
## Appendix A: Complete Token Reference
Every token extracted from the source. §0 CORE TOKENS is the primary AI signal; this appendix is reference material an AI can cross-check against when a curated role is missing.
```css
/* Colours (40) */
--color-bg-dark: #0a0a0a;
--color-bg-hover-dark: #313131;
--color-bg-hover-light: #d8d8d8;
--color-text-primary: #ffffff;
--color-text-on-light: #000000;
--color-text-muted: rgba(255,255,255,0.64);
--color-surface-light: #ffffff;
--color-border-subtle: rgba(255,255,255,0.24);
--primitive-black: #000000;
--primitive-near-black: #0a0a0a;
--primitive-dark-800: #313131;
--primitive-dark-600: #3b3b3b;
--primitive-light-200: #cecece;
--primitive-light-100: #d8d8d8;
--primitive-white: #ffffff;
--primitive-near-white: #fafafa;
--primitive-muted-white: rgba(255,255,255,0.64);
--primitive-border-white: rgba(255,255,255,0.24);
--primitive-mid-grey: #a7a7a7;
--primitive-mid-grey-2: #585858;
--color-bg-page: var(--primitive-near-black);
--color-bg-surface: var(--primitive-near-black);
--color-bg-surface-light: var(--primitive-white);
--color-bg-hover-inverted: var(--primitive-dark-600);
--color-text-ghost: var(--primitive-mid-grey);
--color-text-hover: var(--primitive-white);
--color-border-hover: var(--primitive-border-white);
--color-focus-ring-dark: var(--primitive-white);
--color-focus-ring-light: var(--primitive-near-black);
--btn-primary-bg: var(--color-bg-surface-light);
--btn-primary-bg-hover: var(--primitive-light-200);
--btn-ghost-bg: transparent;
--btn-ghost-bg-hover: var(--color-bg-hover-dark);
--btn-ghost-border: var(--color-border-subtle);
--nav-bg: transparent;
--border-subtle-dark: 1px solid rgba(255, 255, 255, 0.24);
--border-focus: 1px solid #ffffff;
--border-focus-light: 1px solid #0a0a0a;
--transition-color: color var(--dovetail-duration-fast) var(--dovetail-ease-default);
--transition-bg: background-color var(--dovetail-duration-slow) var(--dovetail-ease-default);
/* Typography (31) */
--font-size-xs: 12px; /* 12 elements — e.g. p "4.5/5 g2", p "4.6/5 capterra", p "Connect your data" /* mined from computed styles */ */
--font-size-sm: 14px; /* 44 elements — e.g. p "Connecting the world", p "See more than double", p "Reclaim almost a ful" /* mined from computed styles */ */
--font-size-md: 16px; /* 32 elements — e.g. p "Return on investment", p "Weekly time saved pe", p "Faster shipping" /* mined from computed styles */ */
--font-size-lg: 20px; /* 62 elements — e.g. h3 "Built for trust at s", h3 "Best practices, buil", h3 "Privacy without fric" /* mined from computed styles */ */
--font-size-xl: 24px; /* 5 elements — e.g. h3 "Ingest feedback from", h3 "Identify the next th", h3 "Go from feedback to " /* mined from computed styles */ */
--font-size-2xl: 40px; /* 6 elements — e.g. h3 "How customer insight", p "Dovetail turns your ", p "2.3x" /* mined from computed styles */ */
--font-size-3xl: 56px; /* 2 elements — e.g. h1 "Get total clarity fr", h2 "AI infrastructure yo" /* mined from computed styles */ */
--font-weight-regular: 400; /* 96 elements — e.g. p "Dovetail’s AI centra", p "4.5/5 g2", p "4.6/5 capterra" /* mined from computed styles */ */
--font-weight-medium: 500; /* 67 elements — e.g. h1 "Get total clarity fr", h2 "AI infrastructure yo", h2 "Turn customer feedba" /* mined from computed styles */ */
--font-weight-semibold: 600; /* 1 element — e.g. a "Skip to main content" /* mined from computed styles */ */
--line-height-tight: 18px; /* 18 elements — e.g. p "Connecting the world", a "Enterprise", a "Customers" /* mined from computed styles */ */
--line-height-normal: 19.6px; /* 17 elements — e.g. p "Senior UX Designer", p "Platform", p "Use cases" /* mined from computed styles */ */
--font-sans: "Inter", -apple-system, …, sans-serif;
--font-mono: "JetBrains Mono", "SF Mono", …;
--font-serif: "EB Garamond", Georgia, serif;
--btn-primary-text: var(--color-text-on-light);
--btn-ghost-text: var(--color-text-primary);
--nav-text: var(--color-text-primary);
--nav-text-hover: var(--color-text-primary);
--dovetail-font-size-xs: 12px;
--dovetail-font-size-sm: 14px;
--dovetail-font-size-md: 16px;
--dovetail-font-size-lg: 20px;
--dovetail-font-size-xl: 24px;
--dovetail-font-size-2xl: 40px;
--dovetail-font-size-3xl: 56px;
--dovetail-font-weight-regular: 400;
--dovetail-font-weight-medium: 500;
--dovetail-font-weight-semibold: 600;
--dovetail-line-height-tight: 18px;
--dovetail-line-height-normal: 19.6px;
/* Spacing (23) */
--space-xs: 24px; /* 7 elements — e.g. section .css-2lwgri, section .css-2lwgri, section .css-2lwgri /* mined from computed styles */ */
--space-sm: 32px; /* 19 elements — e.g. section .css-1s2fo9s, section .css-1s2fo9s, section .css-1y9l2ec /* mined from computed styles */ */
--space-md: 48px; /* 2 elements — e.g. section .css-cunfpb, section .css-cunfpb /* mined from computed styles */ */
--space-lg: 64px; /* 3 elements — e.g. section .css-1s2fo9s, section .css-1s2fo9s, section .css-1ea3w3z /* mined from computed styles */ */
--space-xl: 80px; /* 1 element — e.g. section .css-2lwgri /* mined from computed styles */ */
--space-2xl: 124px; /* 26 elements — e.g. section .css-2lwgri, section .css-2lwgri, section .css-1aby5n6 /* mined from computed styles */ */
--dovetail-space-xs: 24px;
--dovetail-space-sm: 32px;
--dovetail-space-md: 48px;
--dovetail-space-lg: 64px;
--dovetail-space-xl: 80px;
--dovetail-space-2xl: 124px;
--space-component-xs: 8px;
--space-component-sm: 16px;
--container-max: 1512px;
--container-content: 1280px;
--container-narrow: 1185px;
--bp-sm: 799px;
--bp-md: 850px;
--bp-lg: 990px;
--bp-xl: 1185px;
--bp-2xl: 1280px;
--bp-3xl: 1512px;
/* Radius (4) */
--radius-sm: 4px; /* 1 element — e.g. a .css-6vl99j "Skip to main content" /* mined from computed styles */ */
--radius-md: 8px; /* 28 elements — e.g. button .e1un6o8d0, button .egaom870 "Product", button .egaom870 "Use cases" /* mined from computed styles */ */
--dovetail-radius-sm: 4px;
--dovetail-radius-md: 8px;
/* Effects (3) */
--btn-disabled-opacity: 0.6;
--shadow-none: none;
--shadow-overlay: none;
/* Motion (4) */
--duration-fast: 0.15s; /* 22 elements — e.g. a, a, a /* mined from computed styles */ */
--duration-base: 0.2s; /* 18 elements — e.g. button, button, a /* mined from computed styles */ */
--duration-slow: 0.3s; /* 28 elements — e.g. button, button, button /* mined from computed styles */ */
--ease-default: ease; /* 142 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: reconstructed-from-computed
siteUrl: dovetail.com
extractionDate: [TBD - note extraction date]
cssCustomProperties: 0 found natively
confidenceLevel: low → moderate (curated tokens are high confidence; colour system is moderate)
```
### Confidence by Category
| Category | Confidence | Method |
|---|---|---|
| Font sizes (7 values) | **High** | Directly mined from computed styles (196 elements total) |
| Font weights (3 values) | **High** | Directly mined from computed styles |
| Line heights (2 named) | **High** | Directly mined from computed styles |
| Heading typography (h1/h2/h3) | **High** | Full computed style extraction |
| Button font (JetBrains Mono) | **High** | Extracted from `button_primary` computed styles |
| Spacing scale (6 values) | **High** | Curated token set, inferred from nav padding + layout digest |
| Border radius (2 values) | **High** | Border-radius census: 1 element at 4px, 28 elements at 8px |
| Motion (durations + easing) | **High** | Mined from computed transitions (188 elements) |
| Colour primitives | **Moderate** | Clustered from state-style rules; background colours confirmed |
| Semantic colour aliases | **Moderate** | Inferred from hover/focus/disabled state rules |
| Section structure | **Low** | Inferred from component inventory + digest; no screenshots |
| Grid column counts | **Low** | Not extractable; inferred from typical SaaS patterns |
| Shadow/elevation values | **Low** | No box-shadow values found in extraction |
### Clustering Method
- **Colour clustering:** Grouped by hue family → all achromatic (black/grey/white spectrum). Near-black cluster: `#000000`, `#0a0a0a`, `#313131`, `#3b3b3b`. Light cluster: `#cecece`, `#d8d8d8`, `#fafafa`, `#ffffff`. All cluster points confirmed from interactive state style extraction.
- **Spacing clustering:** Navigation `padding: 24px` anchors `--dovetail-space-xs`. Button internal padding (`8px 16px`) treated as sub-scale component values. Section scale (32–124px) curated from the provided token set.
- **Radius clustering:** Sharp (0px — headings/body), small (4px — skip link/CTA), medium (8px — majority of interactive elements). No pill-shaped values (≥50px) found. Design is definitively NOT pill-button.
- **Typography clustering:** Font-family split is clean and high-confidence: Inter dominates (body/headings/nav), JetBrains Mono is isolated to button contexts, EB Garamond declared but absent from computed styles (likely used in editorial subpages).More from the gallery
Browse all kits →You may also like

Contra
MITPlayful yet professional design system with teal and purple accents, bold typography, and vibrant accent surfaces—built for modern SaaS platforms and landing pages
03
lightboldsaaslanding-page

DoorDash
MITClean, accessible food-delivery system with DoorDash's signature red accent, light neutral palette, and system-font typography—built for rapid product development
03
lightecommsaasmobile

Mercedes-Benz
MITLuxe, minimalist design system with sharp black-and-white contrast and serif typography, crafted for premium automotive and lifestyle brands
05
darkminimalboldecomm