Handbook
Tokens
The colors, the radii, the shadows and the fonts of ferry-ui, as Tailwind classes and CSS variables.
Handbook
The colors, the radii, the shadows and the fonts of ferry-ui, as Tailwind classes and CSS variables.
A token is a named design value. Each token is a CSS variable. With Tailwind, each token is also a class.
A color token gives one class for each use. For example, --primary gives bg-primary, text-primary and border-primary. Add a slash and a number to set the opacity: bg-destructive/10.
A square sample in a demo uses the bg- class of the token. The table below it gives the class that you use most.
The light values are on :root. The dark values are on .dark. Change the theme of this site to see the two values of each token.
A surface is the background of a part of the screen.
import { cn } from 'ferry-ui'
const SURFACES = [
{ className: 'bg-background', variable: '--background' },
{ className: 'bg-surface-75', variable: '--surface-75' },
{ className: 'bg-surface-100', variable: '--surface-100' },
{ className: 'bg-surface-200', variable: '--surface-200' },
{ className: 'bg-surface-300', variable: '--surface-300' },
{ className: 'bg-selection', variable: '--selection' },
{ className: 'bg-overlay', variable: '--overlay' },
{ className: 'bg-code', variable: '--code-bg' },
]
export default function SurfaceTokens() {
return (
<ul className="grid w-full gap-x-6 gap-y-4 sm:grid-cols-2">
{SURFACES.map((token) => (
<li key={token.variable} className="flex items-center gap-3">
<span aria-hidden="true" className={cn('size-10 shrink-0 rounded-md border border-border-strong', token.className)} />
<span className="flex min-w-0 flex-col font-mono">
<span className="truncate text-[13px] text-foreground">{token.className}</span>
<span className="truncate text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Use |
|---|---|
bg-background | The page, behind all the content |
bg-surface-75 | Card footers, save bars |
bg-surface-100 | Cards, panels, inputs |
bg-surface-200 | Hover states, table headers |
bg-surface-300 | Menus, popovers, dialogs, tooltips |
bg-selection | Selected row, active navigation item |
bg-overlay | Dim layer behind dialogs and sheets |
bg-code | Your own code samples |
The size and these four colors give the hierarchy of the text. ferry-ui does not use bold text.
text-foreground-muted is too faint for text that a person must read.
import { cn } from 'ferry-ui'
const TEXT = [
{ className: 'text-foreground', variable: '--foreground', sample: 'Invoice INV-2041' },
{ className: 'text-foreground-light', variable: '--foreground-light', sample: 'Sent to Acme on October 1' },
{ className: 'text-foreground-lighter', variable: '--foreground-lighter', sample: 'Due in 14 days' },
{ className: 'text-foreground-muted', variable: '--foreground-muted', sample: '/ · $' },
]
export default function TextTokens() {
return (
<ul className="flex w-full flex-col divide-y">
{TEXT.map((token) => (
<li key={token.variable} className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 py-3">
<span className={cn('text-sm', token.className)}>{token.sample}</span>
<span className="flex gap-4 font-mono">
<span className="text-[13px] text-foreground">{token.className}</span>
<span className="text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Use |
|---|---|
text-foreground | Titles, values, body text |
text-foreground-light | Descriptions, inactive items |
text-foreground-lighter | Captions, table headers, icons |
text-foreground-muted | Decorative marks only |
Each element has --border as its default border color. Write border, border-t or divide-y with no color class.
import { cn } from 'ferry-ui'
const BORDERS = [
{ className: 'border', variable: '--border' },
{ className: 'border border-border-strong', variable: '--border-strong' },
{ className: 'border border-border-stronger', variable: '--border-stronger' },
]
export default function BorderTokens() {
return (
<ul className="grid w-full gap-4 sm:grid-cols-3">
{BORDERS.map((token) => (
<li key={token.variable} className={cn('flex flex-col gap-1 rounded-lg bg-surface-100 p-4 font-mono', token.className)}>
<span className="text-[13px] text-foreground">{token.className}</span>
<span className="text-xs text-foreground-lighter">{token.variable}</span>
</li>
))}
</ul>
)
}| Class | Use |
|---|---|
border | Cards, dividers, table rows |
border-border-strong | Controls, outlines of overlays |
border-border-stronger | Controls on hover, dashed outlines |
Primary is the color of the main action, of links and of the focus ring. Brand is the color of your product.
import { cn } from 'ferry-ui'
const PRIMARY = [
{ className: 'bg-primary', variable: '--primary' },
{ className: 'bg-primary-solid', variable: '--primary-solid' },
{ className: 'bg-primary-solid-border', variable: '--primary-solid-border' },
{ className: 'bg-primary-bright', variable: '--primary-bright' },
{ className: 'bg-primary-soft', variable: '--primary-soft' },
{ className: 'bg-primary-foreground', variable: '--primary-foreground' },
{ className: 'bg-ring', variable: '--ring' },
{ className: 'bg-brand', variable: '--brand' },
]
export default function PrimaryTokens() {
return (
<ul className="grid w-full gap-x-6 gap-y-4 sm:grid-cols-2">
{PRIMARY.map((token) => (
<li key={token.variable} className="flex items-center gap-3">
<span aria-hidden="true" className={cn('size-10 shrink-0 rounded-md border', token.className)} />
<span className="flex min-w-0 flex-col font-mono">
<span className="truncate text-[13px] text-foreground">{token.className}</span>
<span className="truncate text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Use |
|---|---|
text-primary | Links, active icons, selected text |
bg-primary-solid | Primary button, checked controls |
border-primary-solid-border | Border of a solid primary fill |
border-primary-bright | Border of a field with the focus |
bg-primary-soft | Counters, selected cards |
text-primary-foreground | Text on primary-solid |
ring-ring | Focus ring |
bg-brand | Logo marks, first chart series |
Use these colors only for a status or for feedback.
import { cn } from 'ferry-ui'
const FEEDBACK = [
{ className: 'bg-success', variable: '--success' },
{ className: 'bg-success-soft', variable: '--success-soft' },
{ className: 'bg-warning', variable: '--warning' },
{ className: 'bg-warning-soft', variable: '--warning-soft' },
{ className: 'bg-warning-border', variable: '--warning-border' },
{ className: 'bg-destructive', variable: '--destructive' },
{ className: 'bg-destructive-solid', variable: '--destructive-solid' },
{ className: 'bg-destructive-soft', variable: '--destructive-soft' },
{ className: 'bg-destructive-border', variable: '--destructive-border' },
{ className: 'bg-info', variable: '--info' },
{ className: 'bg-info-soft', variable: '--info-soft' },
{ className: 'bg-info-border', variable: '--info-border' },
]
export default function FeedbackTokens() {
return (
<ul className="grid w-full gap-x-6 gap-y-4 sm:grid-cols-2">
{FEEDBACK.map((token) => (
<li key={token.variable} className="flex items-center gap-3">
<span aria-hidden="true" className={cn('size-10 shrink-0 rounded-md border', token.className)} />
<span className="flex min-w-0 flex-col font-mono">
<span className="truncate text-[13px] text-foreground">{token.className}</span>
<span className="truncate text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Use |
|---|---|
text-success | Healthy, completed, paid |
bg-success-soft | Success fill |
text-warning | Needs attention |
bg-warning-soft | Warning fill |
border-warning-border | Warning border |
text-destructive | Errors, failed states |
bg-destructive-solid | Confirm button of a destructive dialog |
bg-destructive-soft | Error fill |
border-destructive-border | Error border |
text-info | In progress |
bg-info-soft | Info fill |
border-info-border | Info border |
Five tokens give the colors of chart series. The first series has the brand color.
import { cn } from 'ferry-ui'
const CHARTS = [
{ className: 'bg-chart-1', variable: '--chart-1' },
{ className: 'bg-chart-2', variable: '--chart-2' },
{ className: 'bg-chart-3', variable: '--chart-3' },
{ className: 'bg-chart-4', variable: '--chart-4' },
{ className: 'bg-chart-5', variable: '--chart-5' },
]
export default function ChartTokens() {
return (
<ul className="grid w-full gap-x-6 gap-y-4 sm:grid-cols-2">
{CHARTS.map((token) => (
<li key={token.variable} className="flex items-center gap-3">
<span aria-hidden="true" className={cn('size-10 shrink-0 rounded-md', token.className)} />
<span className="flex min-w-0 flex-col font-mono">
<span className="truncate text-[13px] text-foreground">{token.className}</span>
<span className="truncate text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}The radius shows the role of an element.
import { cn } from 'ferry-ui'
const RADII = [
{ className: 'rounded-sm', variable: '--ferry-ui-radius-sm', size: '4px' },
{ className: 'rounded-md', variable: '--ferry-ui-radius-md', size: '6px' },
{ className: 'rounded-lg', variable: '--ferry-ui-radius-lg', size: '8px' },
{ className: 'rounded-xl', variable: '--ferry-ui-radius-xl', size: '12px' },
{ className: 'rounded-full', variable: 'No variable', size: 'Full' },
]
export default function RadiusTokens() {
return (
<ul className="grid w-full gap-x-6 gap-y-4 sm:grid-cols-2">
{RADII.map((token) => (
<li key={token.className} className="flex items-center gap-3">
<span
className={cn('grid size-12 shrink-0 place-items-center border border-border-strong bg-surface-100 text-xs text-foreground-light', token.className)}
>
{token.size}
</span>
<span className="flex min-w-0 flex-col font-mono">
<span className="truncate text-[13px] text-foreground">{token.className}</span>
<span className="truncate text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Size | Use |
|---|---|---|
rounded-sm | 4px | Chips, square badges, keycaps |
rounded-md | 6px | Controls: buttons, inputs |
rounded-lg | 8px | Containers: cards, tables, dialogs |
rounded-xl | 12px | Large panels (rare) |
rounded-full | Full | Pills, avatars, dots |
Borders give the structure. A shadow shows that an element is above the page.
export default function ElevationTokens() {
return (
<div className="grid w-full gap-6 sm:grid-cols-2">
<div className="flex flex-col gap-1 rounded-lg border bg-surface-100 p-4 font-mono shadow-card">
<span className="text-[13px] text-foreground">shadow-card</span>
<span className="text-xs text-foreground-lighter">--shadow-card</span>
</div>
<div className="flex flex-col gap-1 rounded-lg border border-border-strong bg-surface-300 p-4 font-mono shadow-overlay">
<span className="text-[13px] text-foreground">shadow-overlay</span>
<span className="text-xs text-foreground-lighter">--shadow-overlay</span>
</div>
</div>
)
}| Class | Use |
|---|---|
shadow-card | Cards. No shadow in the dark theme |
shadow-overlay | Menus, popovers, dialogs, toasts |
The ferry-ui/fonts.css file loads Inter and Source Code Pro. Without this file, the browser uses system fonts.
import { cn } from 'ferry-ui'
const FONTS = [
{ className: 'font-sans', variable: '--ferry-ui-font-sans', sample: 'Invoices of October 2026' },
{ className: 'font-mono', variable: '--ferry-ui-font-mono', sample: 'inv_2041 · 4,280.00 USD' },
]
export default function FontTokens() {
return (
<ul className="flex w-full flex-col divide-y">
{FONTS.map((token) => (
<li key={token.variable} className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 py-3">
<span className={cn('text-lg text-foreground', token.className)}>{token.sample}</span>
<span className="flex gap-4 font-mono">
<span className="text-[13px] text-foreground">{token.className}</span>
<span className="text-xs text-foreground-lighter">{token.variable}</span>
</span>
</li>
))}
</ul>
)
}| Class | Fonts |
|---|---|
font-sans | Inter, then system fonts |
font-mono | Source Code Pro, then system monospace |
The class names of shadcn/ui also work. Each alias points to a token of this page. In new code, use the ferry-ui name.
| Alias | Token |
|---|---|
bg-card | surface-100 |
bg-popover | surface-300 |
bg-secondary, bg-muted | surface-200 |
text-muted-foreground | foreground-lighter |
bg-accent | selection |
border-input | border-strong |