Design Token Guide
A deep dive into Open Library's design token system — how it works, why we use it, and how to apply tokens in your code.
Why Tokens?
In a large codebase maintained by many contributors, it's common for one-off CSS values to drift over time. One developer writes color: #1264a3, another writes color: #1366a8, and a third writes color: rgb(18, 100, 163). All three intended the same blue, but now there are three slightly different values to maintain.
Design tokens solve this by defining shared values in one place. Instead of three slightly different blues, every link uses var(--color-link) — one value to find, one value to update.
Beyond consistency, tokens make the codebase manageable at scale: site-wide visual changes come from editing a small set of token files, and theming (dark mode, brand refreshes) is a matter of swapping token values rather than touching every component.
Tokens are CSS custom properties — native to the browser, no build tool required.
The Two-Tier Architecture
Open Library organizes tokens into two tiers:
Tier 1: Primitives
Raw values with no semantic meaning. These are the palette — a curated set of base values that the design system is built from.
/* Colors */
--blue-500: hsl(210, 80%, 50%);
--grey-200: hsl(0, 0%, 88%);
/* Spacing */
--space-4: 4px;
--space-8: 8px;
--space-16: 16px;
/* Border radius */
--border-radius-sm: 2px;
--border-radius-md: 4px;
--border-radius-lg: 8px;Primitives describe what the value is (--blue-500 is a specific shade of blue), but not what it's for. You should rarely use primitives directly in component or template styles.
Tier 2: Semantic Tokens
Semantic tokens reference primitives and give them contextual meaning. They describe what something is for, not what it looks like.
/* These map primitives to intent */
--color-link: var(--blue-600);
--color-surface-primary: var(--white);
--color-border-default: var(--grey-300);
--border-radius-button: var(--border-radius-md);
--border-radius-card: var(--border-radius-lg);
--border-radius-avatar: var(--border-radius-circle);This indirection is what makes the system powerful:
- Visual redesign: Change
--color-linkonce, every link updates. - Dark mode: Remap
--color-surface-primaryfrom white to dark grey. Components don't change. - Brand refresh: Update the primitives, and the entire semantic layer follows.
Which tier to use
/* ✅ Semantic token — describes intent */
.my-card {
border-radius: var(--border-radius-card);
}
/* ❌ Primitive — describes the value, not the purpose */
.my-card {
border-radius: var(--border-radius-lg);
}
/* ❌ Hardcoded — no connection to the system at all */
.my-card {
border-radius: 8px;
}Always use semantic tokens in your styles. If a semantic token doesn't exist for your use case, that's a signal to create one rather than reaching for a primitive or hardcoded value.
Where Tokens Live
Token files are in static/css/tokens/. Each file covers a category:
colors.css— Color primitives and semantic color tokensspacing.css— Spacing scaleborder-radius.css— Border radius primitives and semantic tokenstypography.css— Font families, sizes, and weights
Using Tokens in Practice
In template CSS (BEM classes)
.book-card {
background: var(--color-surface-primary);
border: 1px solid var(--color-border-default);
border-radius: var(--border-radius-card);
padding: var(--space-16);
}In Lit web components
Lit components use Shadow DOM, but design tokens (CSS custom properties) inherit through the shadow boundary. Use them directly in your component's static styles:
static styles = css`
:host {
display: block;
padding: var(--space-16);
}
button {
background: var(--color-primary);
border-radius: var(--border-radius-button);
font-family: var(--font-body);
}
`;Current Status
NOTE
The two-tier token system is being implemented incrementally. The token files in static/css/tokens/ are partially migrated — some are fully two-tier (like border-radius), others are still a mix. See tracking issue #11555 for progress.
When working in an area that still uses hardcoded values, consider migrating them to tokens as part of your change. This incremental approach is how the system grows.