Design tokens

What is a design token?

A design token is a named decision. Not #8f55e5, but action/brand/default. Tokens encode what a value means, not just what it is, and that distinction is what makes a design system scale.

Our token system follows the same three-tier architecture used by GitHub's Primer, Shopify's Polaris, and Material Design 3. The tiers exist to separate concerns: raw values, their meaning, and their application.


The three tiers

Primitive → Semantic → Component
(options) (choices) (application)

Tier 1 — Primitive tokens

Primitives are the raw palette. Every color stop, every spacing increment, every radius value — named and enumerated with no opinion about where they're used.

Token

Value

color/purple/600

#8f55e5

color/purple/700

#6d2fbc

color/stone/950

#2c2935

color/green/50

#f0fdf4

Primitives answer: what colors exist?
They do not answer: which color should a button use? or what makes text readable?

Tier 2 — Semantic tokens

Semantic tokens are where design decisions live. They reference a primitive, but their name carries intent — role, state, and context. This is the tier you use for almost everything.

Token

Resolves to

Meaning

action/brand/default

color/purple/600

The brand color for interactive elements at rest

action/brand/hover

color/purple/700

Brand color on hover

content/primary

color/stone/950

Primary text on light surfaces

content/secondary

color/stone/800

Supporting text, labels

content/disabled

color/stone/400

Inactive or unavailable text

surface/base

color/white

Default card and panel background

surface/page

color/stone/50

Page-level background

surface/sunken

color/stone/100

Recessed areas, input backgrounds

feedback/success/surface

color/green/50

Success state background

feedback/error/border

color/red/400

Error state border

border/base

color/stone/300

Standard border

border/focus

color/purple/600

Keyboard focus ring

Semantic tokens answer: what is this color for?

They are the primary design vocabulary for Homebase. When you're designing a new screen, a new pattern, or a variant of something that isn't a design system component — semantic tokens are your tools.

Tier 3 — Component tokens

Component tokens are scoped to a specific design system component. They sit above semantic tokens and give each component a single override point — essential for theming, dark mode, and future customization.

Token

Resolves to

button/primary/surface/default

action/brand/default

button/primary/surface/hover

action/brand/hover

input/border/focus

action/brand/active

input/border/error

feedback/error/border

input/surface/disabled

action/neutral/disabled

Component tokens answer: how does this specific component apply meaning?


Your default: semantic tokens

For the overwhelming majority of design work, semantic tokens are the right choice. Whether you're composing a new screen layout, designing a custom pattern, building a one-off state, or creating anything that uses the Homebase language — semantic tokens are what you reach for.

How to think about semantic token groups

surface/* — Backgrounds for frames, panels, cards, and containers.

  • Use surface/base for cards and elevated containers
  • Use surface/page for the main page background
  • Use surface/sunken for recessed areas like input fields or sidebar backgrounds
  • Use surface/overlay for floating elements like modals and popovers

content/* — Text and icon color.

  • Use content/primary for headings and primary body text
  • Use content/secondary for supporting text, subtitles, metadata
  • Use content/tertiary for timestamps, helper text
  • Use content/placeholder for placeholder text inside inputs
  • Use content/disabled for anything inactive
  • Use content/inverse for text on dark backgrounds
  • Use content/brand for Homebase-branded text callouts

action/* — Interactive element states. Covers all the fill, hover, active, and disabled states for buttons, tabs, chips, and any other clickable element.

  • action/brand/* — primary brand actions (purple filled buttons, active nav states)
  • action/brand-muted/* — low-emphasis brand surfaces (ghost buttons, tag fills)
  • action/selected/* — selection states, highlighted rows
  • action/neutral/* — secondary, tertiary actions on light surfaces
  • action/destructive/* — delete and danger actions
  • action/link/* — text links

border/* — All stroke and outline colors.

  • border/subtle — low-emphasis dividers
  • border/base — standard borders on inputs, cards, dividers
  • border/strong — emphasized borders, hover states
  • border/focus — keyboard focus rings
  • border/active — selected or active element borders

feedback/* — Status and alert states.

  • Each feedback category (success, warning, error, info, neutral, accent) has three semantic slots: surface, border, and content
  • Apply all three to keep status patterns consistent

Matching states with tokens

Every interactive element needs a full set of states. Semantic tokens are organized to make this natural:

State

Token pattern

Default

action/brand/default

Hover

action/brand/hover

Active / pressed

action/brand/active

Disabled

action/brand/disabled

Focus

border/focus

Never hard-code a state by picking a darker primitive. Use the token built for that state.


Why not primitives?

Primitives are not for use in design work. Here's why:

They break theming. Semantic tokens swap their underlying values in dark mode, high contrast, or white-label contexts. A primitive like color/purple/600 is always #8f55e5 — it doesn't adapt. If you've used it directly, your design breaks every time we update a palette or ship a new theme.

They create arbitrary choices. When five different shades of gray are all technically valid text colors, every designer makes a different call. Semantic tokens resolve that ambiguity by establishing one right answer per use case. content/primary is primary text — there's nothing to decide.

They undermine maintainability. A semantic token is a contract. When we need to shift the brand color, we change action/brand/default in one place and every component that references it updates automatically. Designs built on primitives require a manual find-and-replace across every file.

They communicate nothing. color/purple/600 tells a developer the color. action/brand/default tells them the intent, the role, and the expected behavior. Intent is what makes a design system readable and auditable.

GitHub's Primer puts it directly: "Base color tokens are only to be used as a reference for functional tokens and should never be used directly in code or design."

The migration context

Our previous token library was a mix of semantic styles, primitive references, and old naming conventions (surface/primary, text/default, color/purple100). The Token Migrator plugin maps all of these to their correct semantic equivalents in the new system — it's a direct one-to-one translation built from the full library.

If you're working in an older file and see legacy styles or old variable names, use the Token Migrator plugin (link coming soon form Taylor) to update your selections. Every legacy token has an explicit mapping to the new semantic layer. When the plugin flags something it can't auto-resolve, that's a prompt to choose the right semantic token manually — a choice that makes the intent explicit.


Component tokens — for design system authors only

Component tokens exist for a specific reason: to give each design system component one clean override point per property. They are used inside a component's definition, not in layouts or screens that consume that component.

Use component tokens if you are:

  • Authoring a new design system component (e.g. button, input, chip, dialog)
  • Creating a component that is a direct family extension of an existing one — for example, a new input variant like a tag input, a currency input, or a date picker that extends the base input component's visual contract

Do not use component tokens if you are:

  • Building a new product screen or feature
  • Designing a one-off UI pattern
  • Creating something that won't live in the design system library
  • Customizing a component for a single use case — reach for semantic tokens instead

What "same family" means

A family extension shares a core visual identity and interaction model with its parent. A date picker and a text feild are both inputs that share size, borders, fills, etc.. A custom schedule filter panel is not — it may use an input, but it is a product pattern, not an input variant.

Design it with semantic tokens.

Component tokens chain from semantic tokens, never directly from primitives:
button/primary/surface/hover
→ action/brand/hover
→ color/purple/700

If you're writing a component token that points at a primitive instead of a semantic token, that's a signal something is wrong.


Reference: token tiers at a glance

Tier

Who uses it

What it is

Example

Primitive

System authors only

Raw color, spacing, radius values

color/purple/600 = #8f55e5

Semantic

Everyone — the default choice

Named decisions about role and intent

action/brand/defaultcolor/purple/600

Component

DS component authors only

Component-scoped application of semantics

button/primary/surface/defaultaction/brand/default


Quick checklist

Before applying any token, ask:

  1. Is there a semantic token for this use case? Start here. The answer is almost always yes.
  2. Am I applying a state (hover, active, disabled)? Use the action/* group for the matching state — don't pick a darker primitive.
  3. Is this a feedback/status pattern? Use feedback/{category}/surface, /border, /content together.
  4. Is this a brand-new design system component or fork or an existing component? Component tokens are appropriate here.
  5. Am I about to pick a primitive? Pause. Find the semantic token that carries this intent instead, or reach out to Designbase if you can't find something that fits your needs.