# Tokens

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.

## Surfaces

A surface is the background of a part of the screen.

```tsx
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 |

## Text

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.

```tsx
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 |

## Borders

Each element has `--border` as its default border color. Write `border`, `border-t` or `divide-y` with no color class.

```tsx
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 and brand

Primary is the color of the main action, of links and of the focus ring. Brand is the color of your product.

```tsx
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 |

## Feedback

Use these colors only for a status or for feedback.

```tsx
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 |

## Charts

Five tokens give the colors of chart series. The first series has the brand color.

```tsx
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>
  )
}
```

## Radius

The radius shows the role of an element.

```tsx
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 |

## Elevation

Borders give the structure. A shadow shows that an element is above the page.

```tsx
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 |

## Fonts

The `ferry-ui/fonts.css` file loads Inter and Source Code Pro. Without this file, the browser uses system fonts.

```tsx
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 |

## shadcn aliases

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` |

## Next steps

- Read [Styling](/docs/handbook/styling) for the rules of `className`.
- Read [Theming](/docs/handbook/theming) to change the value of a token.
