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/default → color/purple/600 |
|
Component |
DS component authors only |
Component-scoped application of semantics |
button/primary/surface/default → action/brand/default |
Quick checklist
Before applying any token, ask:
- Is there a semantic token for this use case? Start here. The answer is almost always yes.
- Am I applying a state (hover, active, disabled)? Use the action/* group for the matching state — don't pick a darker primitive.
- Is this a feedback/status pattern? Use feedback/{category}/surface, /border, /content together.
- Is this a brand-new design system component or fork or an existing component? Component tokens are appropriate here.
- 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.