Design Tokens
Design tokens are the shared styling vocabulary for cardano.org. When you write CSS, reach for a token instead of a raw value. This keeps spacing, color, and motion consistent across pages built by different contributors, and it means a change to the palette or scale happens in one place.
All tokens are CSS custom properties defined in src/css/custom.css. They are global, so any *.module.css file can use them without an import.
yarn test:css fails the build if a --site-* or other project variable is used without being defined. Do not invent new hardcoded hex colors or one-off spacing values; use or extend the tokens below.
How tokens are organized
Two namespaces, by design:
--ifm-*are Infima (Docusaurus) variables. Use these for anything the framework already owns: the brand color and its ramp, neutral greys, and surfaces. Docusaurus's own components (navbar, buttons, admonitions) read them too.--site-*are this project's own tokens for everything Infima does not provide: semantic status colors, spacing, radius, elevation, motion, and focus.
Neutrals have no --site-* tokens on purpose. Use Infima's theme-aware emphasis scale rather than a second grey palette:
| Use | Token |
|---|---|
| Body text | var(--ifm-font-color-base) |
| Strong text | var(--ifm-color-emphasis-900) |
| Muted / secondary text | var(--ifm-color-emphasis-600) |
| Borders, dividers | var(--ifm-color-emphasis-200) / -300 |
| Card / panel background | var(--ifm-background-surface-color) |
| Page background | var(--ifm-background-color) |
These already invert correctly in dark mode, which is why you should not reach for a hardcoded grey.
Color
Brand
The Cardano blue and its ramp live on Infima's primary variables. The ramp is a proper mono-hue ladder: darker variants for hover and active states, lighter variants for soft fills.
| Token | Light | Use |
|---|---|---|
--ifm-color-primary | #0033AD | Brand blue, default |
--ifm-color-primary-dark | #002a8e | Hover on filled buttons/links |
--ifm-color-primary-darker | #00257d | Active / pressed |
--ifm-color-primary-darkest | #001f68 | Strongest accent |
--ifm-color-primary-light … -lightest | #0038be … #0042e1 | Lighter accents |
For rgba() use the RGB triple: rgba(var(--ifm-color-primary-rgb), 0.5).
In dark mode the whole ramp shifts to a lighter blue automatically; you do not need to handle it per component.
Semantic status
One canonical value each, with dark-mode variants already defined:
| Token | Light | Meaning |
|---|---|---|
--site-success | #2da06a | Success, positive |
--site-warning | #d97706 | Warning, caution |
--site-danger | #dc3545 | Error, destructive |
--site-info | brand blue | Informational |
Brand tints
Translucent primary, for soft fills and hover backgrounds (prefer these over a hardcoded rgba(0,51,173,…)):
--site-tint-weak:rgba(var(--ifm-color-primary-rgb), 0.06)--site-tint-strong:rgba(var(--ifm-color-primary-rgb), 0.12)
Spacing
An 8px-based scale. Use it for padding, margins, and gaps.
| Token | Value |
|---|---|
--site-space-3xs | 0.25rem (4px) |
--site-space-2xs | 0.5rem (8px) |
--site-space-xs | 0.75rem (12px) |
--site-space-sm | 1rem (16px) |
--site-space-md | 1.5rem (24px) |
--site-space-lg | 2rem (32px) |
--site-space-xl | 3rem (48px) |
--site-space-2xl | 4rem (64px) |
--site-space-3xl | 6rem (96px) |
For vertical rhythm between sections, the Spacer Box component wraps sm / lg / 3xl.
Corner radius
| Token | Value | Use |
|---|---|---|
--site-radius-sm | 6px | Small controls |
--site-radius-md | 8px | Default card |
--site-radius-lg | 12px | Large cards |
--site-radius-xl | 16px | Feature panels |
--site-radius-pill | 999px | Pills, chips |
Circular elements use border-radius: 50% directly.
Elevation
Four shadow steps, each with a stronger dark-mode variant already defined:
| Token | Use |
|---|---|
--site-shadow-xs | Subtle lift |
--site-shadow-sm | Resting card |
--site-shadow-md | Raised card |
--site-shadow-lg | Hover / floating |
Motion
| Token | Value | Use |
|---|---|---|
--site-duration-fast | 150ms | Small state changes |
--site-duration-base | 200ms | Default |
--site-duration-slow | 300ms | Larger transitions |
--site-ease | cubic-bezier(0.4, 0, 0.2, 1) | Standard easing |
--site-lift | -2px | Canonical hover lift (transform: translateY(var(--site-lift))) |
A typical hover transition:
.card {
transition: transform var(--site-duration-base) var(--site-ease),
box-shadow var(--site-duration-base) var(--site-ease);
}
.card:hover {
transform: translateY(var(--site-lift));
box-shadow: var(--site-shadow-lg);
}
Wrap non-essential animation in @media (prefers-reduced-motion: reduce) and disable it there.
Focus
There is a global keyboard-focus baseline in custom.css, so most interactive elements get a consistent ring for free:
:focus-visible {
outline: 2px solid var(--ifm-color-primary);
outline-offset: 2px;
}
Do not remove focus outlines. If a component needs a shadow-style ring instead of an outline, use --site-focus-ring.
Breakpoints
Breakpoints are not tokens: CSS custom properties cannot be used inside @media queries, and the project has no preprocessor. Use these three canonical values so component collapse points stay in sync with the navbar and sidebar:
| Width | Use |
|---|---|
996px | Primary. Matches the Docusaurus navbar/sidebar collapse. |
768px | Tablet. |
480px | Mobile. |
yarn test:css blocks the 966px typo (a transposition of 996px) from coming back.
Adding a token
If you genuinely need a value the scales do not cover, add it to the relevant block in src/css/custom.css (with its dark-mode variant if it is a color or shadow) rather than hardcoding it in a component. Keep the naming consistent with the existing --site-* groups.