Back to Ghost

Tokens

apps/shade/src/docs/tokens.mdx

6.50.05.0 KB
Original Source

import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Tokens / Tokens Guide" /> <div className="sb-doc">

Tokens

<p className="excerpt">Tokens are Shade's visual atoms — colours, type sizes, spacing, radii, shadows, motion. Every primitive, component, and pattern resolves down to these. Browse the live galleries in the sidebar; the rules for using them are short.</p>

The galleries

  • Colors — surface, text, border, state, chart, and the raw palette.
  • Typography — font families, size ramp, line-heights.
  • Spacing — the 0.4rem base unit and the semantic gap scale primitives use.
  • Radius — numeric and semantic radii (radius-control, radius-surface, radius-pill).
  • Shadows — elevation tokens for cards, popovers, and modals.
  • Motion — durations, easings, and named animations.
  • Breakpoints — responsive cut-offs backing Tailwind's md: / lg: variants.

Toggle light/dark from the Storybook toolbar to see semantic tokens flip.

Two flavours

Semantic tokens are named by purpose — --background, --text-primary, --border-default, --surface-elevated. They flip between light and dark mode automatically. Reach for these by default.

Raw tokens are concrete values — --color-gray-500, --text-base, --spacing. They never change between modes. Use only when no semantic token fits (chart series, brand assets, illustrations).

Source files

  • apps/shade/theme-variables.css — semantic tokens + dark-mode overrides.
  • apps/shade/tailwind.theme.css — Tailwind @theme block: raw catalogue and bindings.
  • Semantic color variables should resolve directly to raw --color-* tokens, not to another semantic token. For example, prefer --text-primary: var(--color-black) over --text-primary: var(--foreground).

Rules of consumption

  • Prefer Tailwind utilities — bg-background, text-foreground, rounded-md — over inline styles.
  • Never hard-code hex or hsl() values for UI chrome. They don't theme and don't dark-mode. Chart tokens are the current exception until the chart palette has its own raw scale.
  • Inside stylesheets, use var(--token) directly. Don't wrap variables in color functions.
  • Don't write dark: variants for colour. The tokens do that. (Exceptions: assets like logos and illustrations.)

Surface elevation

Surfaces are ordered by how high they sit. Pick by what's behind the element, not by component type.

  1. --background — the page canvas. Default for body, app shell, editor.
  2. --surface-elevated — one step above page. Use for sidebars, cards, top bars, sticky headers, the elevated edges of stats panels.
  3. --surface-elevated-2 — one step above elevated. Reserved for floating menus that open ON TOP of an elevated surface — DropdownMenu, Select, Popover, and the sidebar user menu. The job is to make the popover read as floating above its trigger.

In light mode all three flatten to near-white, so borders and shadows carry the visual elevation. In dark mode they're three distinct colours so layered surfaces stay legible without relying on borders alone.

Interactive surfaces

Hover, active, and selected states are their own tokens — don't reach for raw greys or apply /30 opacity modifiers ad-hoc.

TokenUse for
--interactive-hoverGeneric hover: dropdown items, menu items, list rows, filter options. The catch-all.
--button-hoverOutline / dropdown Button hover. Currently aliased to --interactive-hover; kept separate so the button surface can diverge later.
--tab-hover / --tab-activeTabs (button, button-sm, pill, kpis), PageMenu, sidebar menu items, active Toggle (dark).
--table-row-hoverShade Table row hover. Also used by visually-table-like lists (analytics top posts, comments list, members sticky cell).

In dark mode --interactive-hover, --button-hover, and --tab-hover are translucent (gray-900/30) so they composite over whatever surface sits behind them. --table-row-hover is opaque (it matches --surface-elevated) — use it where you need an opaque fill, like sticky table cells where content scrolls underneath.

Form control borders

Inputs, textareas, outline Buttons, dropdown triggers, and the inputSurface recipe use --control-border, not --border-default. In dark mode it lifts a step above the page border so form controls keep enough contrast against the page surface. Borders inside cards or popovers can keep using --border-default.

DropdownMenu, Select, and Popover (the three Radix-backed floating menus that Filters uses) share one visual recipe: bg-surface-elevated-2 + border border-border/60 dark:border-border/30 + shadow-md. Treat them as one component for any surface decision — change all three together.

</div>