Dovetail
MIT
A dark-first React component system with precise spacing and restrained typography, built for product teams prioritising clarity and developer efficiency
Colour (40)
color.navbgtransparent
color.btnghostbgtransparent
color.borderfocus1px solid #ffffff
color.colorbgdark#0a0a0a
color.colorbgpagevar(--primitive-near-black)
color.btnprimarybgvar(--color-bg-surface-light)
color.transitionbgbackground-color var(--dovetail-duration-slow) var(--dovetail-ease-default)
color.btnghostbordervar(--color-border-subtle)
color.colorbgsurfacevar(--primitive-near-black)
color.colortextghostvar(--primitive-mid-grey)
color.colortexthovervar(--primitive-white)
color.colortextmutedrgba(255,255,255,0.64)
color.primitiveblack#000000
color.primitivewhite#ffffff
color.btnghostbghovervar(--color-bg-hover-dark)
color.transitioncolorcolor var(--dovetail-duration-fast) var(--dovetail-ease-default)
color.borderfocuslight1px solid #0a0a0a
color.bordersubtledark1px solid rgba(255, 255, 255, 0.24)
color.colorbghoverdark#313131
color.colorborderhovervar(--primitive-border-white)
color.colortextonlight#000000
color.colortextprimary#ffffff
color.primitivedark600#3b3b3b
color.primitivedark800#313131
color.primitivemidgrey#a7a7a7
color.btnprimarybghovervar(--primitive-light-200)
color.colorbghoverlight#d8d8d8
color.colorbordersubtlergba(255,255,255,0.24)
color.colorsurfacelight#ffffff
color.primitivelight100#d8d8d8
color.primitivelight200#cecece
color.primitivemidgrey2#585858
color.colorfocusringdarkvar(--primitive-white)
color.primitivenearblack#0a0a0a
color.primitivenearwhite#fafafa
color.colorbgsurfacelightvar(--primitive-white)
color.colorfocusringlightvar(--primitive-near-black)
color.primitivemutedwhitergba(255,255,255,0.64)
color.colorbghoverinvertedvar(--primitive-dark-600)
color.primitiveborderwhitergba(255,255,255,0.24)
Spacing (23)
spacing.spacecomponentxs8px
spacing.spacecomponentsm16px
spacing.spacexs24px
spacing.dovetailspacexs24px
spacing.spacesm32px
spacing.dovetailspacesm32px
spacing.spacemd48px
spacing.dovetailspacemd48px
spacing.spacelg64px
spacing.dovetailspacelg64px
spacing.spacexl80px
spacing.dovetailspacexl80px
spacing.space2xl124px
spacing.dovetailspace2xl124px
spacing.bpsm799px
spacing.bpmd850px
spacing.bplg990px
spacing.bpxl1185px
spacing.containernarrow1185px
spacing.bp2xl1280px
spacing.containercontent1280px
spacing.bp3xl1512px
spacing.containermax1512px
Radius (4)
radiussm4px
dovetailradiussm4px
radiusmd8px
dovetailradiusmd8px
Shadow (3)
effect.shadownonenone
effect.shadowoverlaynone
effect.btndisabledopacity0.6
# 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

Vercel
MITClean, minimal developer-focused design system built on Geist typography with light surfaces, precise spacing, and fast interactions for modern web applications
00
lightminimaldeveloper-toolsaas

Sentry
MITDark, minimal design system for developer tools with a sophisticated purple palette and refined typography, tailored for error monitoring and performance platforms
00
darkminimaldeveloper-toolsaas

OpenAI
MITClean, minimal design system for AI-native products built on Next.js and Tailwind, featuring a light monochromatic palette with precise token-based spacing and typography
02
lightminimalsaasdeveloper-tool