White-label design system

One component set. Every brand you ship.

Dovetail ships unbranded on purpose. Components read semantic tokens, tokens read a brand theme, and swapping the theme changes every surface at once, without a fork.

npm install @dovetail-ds/react react react-dom

No lock-inDTCG tokensLight and dark

Why it holds

Three tiers, referenced one way

Primitives name values

A ramp of OKLCH colours, a dimension scale, a type scale. Nothing here knows what it is for.

Semantics name jobs

Surface, text, border, and their paired roles. This is the only tier a component is allowed to read.

Components name parts

Button background, input border. Retuned per context without touching the component.

See it run

Whole screens from the same parts

A landing page and a product dashboard, composed only from Dovetail components. Nothing in them names a colour.

  • Light and dark from the same roles
  • Icons, photos, video and illustration slots
  • Retheme both at once in Configure
Proof

Real components, not pictures of them

Every tile here is the live component, drawn from the same bundle you install. Change the theme and they all follow.

Components114across eleven families
Token tiers3referenced one way
ContrastAAchecked on every pairing
Theming

Your brand arrives last, as one file

Components read semantic roles. Roles read your theme. Swap the theme and every surface changes at once, light and dark.

Pick a brand colour

An exact hex becomes a full ramp, with its contrast checked.

Set shape and type

Radius, families, density and icons, all as tokens.

Export the theme

A CSS file of overrides. No fork, no build step.

Documentation

Everything in the docs

Start here

Install it. Dovetail is on npm as @dovetail-ds/react. React 18 or newer is the only peer dependency; install react-dom alongside, since your app renders with it and npm won't add it for you.

npm install @dovetail-ds/react react react-dom

Import the stylesheets once at your app's root, then use the components:

import "@dovetail-ds/react/fonts.css"; // optional: Geist from Google Fonts
import "@dovetail-ds/react/styles.css";
import "./dovetail-theme.css"; // your theme, after styles.css

import { Button, Section, Stack } from "@dovetail-ds/react";

Dark mode needs no second stylesheet. Put class="dark" on <html>, or on any element to darken one band. Interactive components ship with "use client", so they work in the Next.js App Router.

Set it up with Claude Code. The package ships a skill that asks about your brand (or takes a theme downloaded from Configure), writes your theme, and wires it into your app:

mkdir -p .claude/skills
cp -r node_modules/@dovetail-ds/react/skills/dovetail-setup .claude/skills/

Then ask Claude to "set up Dovetail with our brand".

Without a bundler. Link the stylesheets directly, then optionally a theme:

<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="tokens/themes/theme-editorial.css">

Building with it. Read CLAUDE.md for the authoring rules, then the component's own .md file before you use it.

Theming it. guidelines/theming.md walks from a brand palette to a working theme.


The three tiers

Open full card

Component reads semantic, semantic reads primitive. Nothing skips or reverses.

How the system is put together

styles.css              Global entry. Import lines only.
tokens/
  dovetail.tokens.json  DTCG source of truth; everything else is generated from it
  primitive/            Tier 1: raw values, no meaning
  semantic/             Tier 2: purpose. The themeable layer.
  component/            Tier 3: per-component escape hatches
  themes/               base-dark, theme-editorial, theme-mono, density-compact
  base.css              Minimal element defaults
components/             React primitives, grouped by concern
guidelines/             Foundations prose and spec cards
integrations/           Adapters that turn a CMS payload into blocks
tools/                  Style Dictionary config and the token pipeline

Three tiers, one direction

Three tiers, referenced in one direction only:

component  →  semantic  →  primitive

A component reads semantic tokens. A semantic token reads a primitive. Nothing reads upward, and nothing skips a tier. This is the rule the whole system rests on, because it is what makes a rebrand a one-file change instead of a migration.

TierAnswersExampleWho may read it
PrimitiveWhat values exist?--dt-color-primary-600Semantic tokens only
SemanticWhat is this value for?--dt-surface-actionComponents, product code
ComponentHow does this component use it?--dt-button-primary-bgThat component only

Two conventions make the semantic tier trustworthy:

No value words above Tier 1. A token named --dt-surface-blue is a lie the moment someone rebrands to green. Colour names live in the primitive tier and nowhere else.

Backgrounds and foregrounds ship as pairs. --dt-surface-action always travels with --dt-text-on-action. Contrast is verified on the pair. Using one without the other is how a button ends up with 2:1 text.

The documented exception: data visualisation reads primitives directly, because chart series need categorical distinctness rather than semantic meaning.


Rules checklist

Self-audit against this before shipping anything built on Dovetail.

  1. Every colour, dimension, radius, shadow, and duration is a token reference. No literals.
  2. Components read semantic tokens. Only chart code reads primitives, and it says why.
  3. Every background role is used with its paired foreground role.
  4. All spacing is a multiple of 4px, taken from a --dt-space-* token.
  5. Type uses a complete role (family, size, line-height, weight, tracking), not a loose size.
  6. Interactive elements have a visible :focus-visible ring and a 44px minimum hit area on touch.
  7. Contrast clears WCAG 2.2 AA: 4.5:1 body text, 3:1 large text and non-text.
  8. The screen works in light and dark without component changes.
  9. Motion uses a --dt-motion-* role and respects prefers-reduced-motion.
  1. Copy is sentence case, verb-first on actions, no emoji.

Read the full README · Authoring rules

Ship your brand, not ours

Free and open. Take the stylesheets and tokens, or theme it here first.

Download