apps/shade/src/docs/tokens.mdx
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Tokens / Tokens Guide" /> <div className="sb-doc">radius-control, radius-surface, radius-pill).md: / lg: variants.Toggle light/dark from the Storybook toolbar to see semantic tokens flip.
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).
apps/shade/theme-variables.css — semantic tokens + dark-mode overrides.apps/shade/tailwind.theme.css — Tailwind @theme block: raw catalogue and bindings.--color-* tokens, not to another semantic token. For example, prefer --text-primary: var(--color-black) over --text-primary: var(--foreground).bg-background, text-foreground, rounded-md — over inline styles.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.var(--token) directly. Don't wrap variables in color functions.dark: variants for colour. The tokens do that. (Exceptions: assets like logos and illustrations.)Surfaces are ordered by how high they sit. Pick by what's behind the element, not by component type.
--background — the page canvas. Default for body, app shell, editor.--surface-elevated — one step above page. Use for sidebars, cards, top bars, sticky headers, the elevated edges of stats panels.--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.
Hover, active, and selected states are their own tokens — don't reach for raw greys or apply /30 opacity modifiers ad-hoc.
| Token | Use for |
|---|---|
--interactive-hover | Generic hover: dropdown items, menu items, list rows, filter options. The catch-all. |
--button-hover | Outline / dropdown Button hover. Currently aliased to --interactive-hover; kept separate so the button surface can diverge later. |
--tab-hover / --tab-active | Tabs (button, button-sm, pill, kpis), PageMenu, sidebar menu items, active Toggle (dark). |
--table-row-hover | Shade 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.
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.