WorkArticlesContact
© 2026 AycanBack to top ↑
← All articles

Hygraph · Baukasten Design System

Creating design tokens for Hygraph

August 18, 2023

A perceptual color palette tells you which colors exist. It doesn't tell you which one to pick. That's the job of design tokens, and Baukasten didn't have good ones.

What wasn't working

Our old setup had a few recurring problems:

  • Core colors were used directly in designs. A name like gray-500 gives no clue where it belongs, so designers couldn't tell primary-400 from primary-500 and made arbitrary choices.
  • Every design needed a color check, because non-semantic names were so error-prone.
  • Semantic names only existed at component level, which meant far too many tokens with far too long names.
  • No CSS custom properties, so theming was hard. On top of that, our CSS-in-JS setup wasn't supported in React Server Components.

Goals

We set ourselves a short list:

  1. A clear token taxonomy where the name explains where the token is used.
  2. Avoid component-specific tokens unless a design consideration truly outweighs system thinking.
  3. The same names in Figma and in code, so design and engineering share one language.
  4. Keep it simple, with no useless parts.

Two layers, not three

Many systems use three layers: core tokens hold values, semantic tokens point at core tokens, and component tokens point at both. We dropped the third.

  1. 1. Color value

    #5d57e1

  2. 2. Core token

    indigo-600

  3. 3. Semantic token

    bg-primary

  4. 4. Component token

    button-bg-primary

Component tokens look tidy at first and become a maintenance burden fast. Nate Baldwin, who created Adobe Spectrum, has written about Spectrum growing to more than 210,000 tokens in an 18MB JSON file. Material 3, Atlassian, Polaris and Loom's Lens all use semantic tokens only. So did we.

A naming convention

Every token follows one template: property, context, variant, state.

A table of token name parts: property (bg, fg), context (link, border, icon, accent), color or variant (base, primary, success, warning, danger), state (hover, pressed, selected)
The naming template from our first proposal, v0.1-alpha.

By the time it shipped, the template had settled into four families: bg, fg-text, fg-icon and border, plus fg-link for links. A few real examples:

  • bg-base

    Page body

    #ffffff

  • bg-surface-5

    Container backgrounds

    #eef0f9

  • bg-primary

    Solid primary buttons

    #5d57e1

  • bg-danger-muted

    Soft danger backgrounds

    #fff0f0

  • fg-text-base

    Body copy

    #343a55

  • fg-icon-primary

    Primary icons

    #5d57e1

  • border-input-selected

    Selected inputs

    #5d57e1

States live on the background

Most background tokens come with state variants (hover, pressed, selected and disabled), and they always move in the same direction.

bg-primary states

Aa

muted

#f3f3ff

Aa

disabled

#d1d0fe

Aa

default

#5d57e1

Aa

hover

#4640b9

Aa

pressed

#332e87

Text works differently. Our rule: state is always shown by the background, except for disabled. A button's label stays the same color whether it's hovered or pressed, so text tokens have no hover or pressed variants. Links are the one exception: they have no container to change, so fg-link-base-hover exists and fg-text-base-hover doesn't.

Instead of states, text gets a strength scale. Everything down to muted passes AA on white:

fg-text · contrast on white

Aa

base-strong

#181b2a

17.09:1

Aa

base

#343a55

11.16:1

Aa

base-soft

#495173

7.76:1

Aa

base-muted

#5e6993

5.36:1

Aa

base-ghost

#7782ad

3.76:1

Surfaces

Surfaces are background colors that give designers flexibility for containers. They're numbered by depth, so bg-surface-1 is barely off-white and bg-surface-90 is almost black. A separate bg-surface-dark-* set stays dark in every theme. We needed it for the data grid.

bg-surface

Aa

surface-1

#f9fbff

Aa

surface-5

#eef0f9

Aa

surface-10

#dfe2ee

Aa

surface-70

#949dc0

Aa

surface-90

#181b2a

Accent colors, more than a dozen hues like teal, olive and rose, exist too, but only for pills and schema field icons. They're deliberately kept out of everything else.

How it ships

Tokens are CSS custom properties on :root, stored as RGB channels. Each one becomes a Tailwind utility with exactly the same name, so the class in code is the token in Figma:

tokens.css
:root {
  --bg-primary: 93 87 225;
  --bg-primary-hover: 70 64 185;
  --fg-text-base: 52 58 85;
}
 
.bg-primary {
  --tw-bg-opacity: 1;
  background-color: rgba(var(--bg-primary) / var(--tw-bg-opacity, 1));
}

Because values are channels, any token can take an opacity without a new variable, and switching themes means swapping one set of custom properties.

The result

Baukasten now has around 240 color tokens, and none of them is tied to a single component. Designers pick colors by role instead of by shade, engineers write the same names in their classes, and the name itself says where a color belongs.