Styling with tokens
Use the same CSS variables as the components, so your own UI matches and follows the theme.
The styled components read semantic tokens: CSS custom properties named after their role, not their value. Use them for your own layout, and it will match the components in every theme:
.panel {
display: grid;
gap: var(--ct-space-group-gap);
padding: var(--ct-space-panel-inset);
border: var(--ct-border-width-default) solid var(--ct-color-border-default);
border-radius: var(--ct-radius-panel);
background: var(--ct-color-bg-surface);
color: var(--ct-color-fg-default);
font: var(--ct-text-body);
letter-spacing: var(--ct-text-body-letter-spacing);
}Token groups
| Variables | For |
|---|---|
--ct-color-bg-* | backgrounds by layer: canvas, surface, raised, inset, hover |
--ct-color-fg-* | text and icons: default, muted, subtle, disabled |
--ct-color-border-* | subtle dividers, default outlines, strong control edges, focus rings |
--ct-color-<status>-* | accent, success, warning, danger: fill, fill-hover, on-fill, fg, bg, border |
--ct-color-overlay-* | scrim: the translucent backdrop behind dialogs and drawers |
--ct-color-syntax-* | code highlighting: constant, string, comment, keyword, parameter, function, string-expression, punctuation, link |
--ct-space-* | padding and gaps: control-inset, -inset-lg, control-gap, -gap-lg, group-gap, panel-inset, section-gap |
--ct-size-* | control heights: control-sm, -md, -lg; icon sizes: icon-sm, -md, -lg; overlay sizes: dialog-*, drawer-*, popover |
--ct-radius-* | control, panel, round |
--ct-border-width-* | default, focus |
--ct-shadow-* | raised for popovers and menus, overlay for dialogs and drawers |
--ct-layer-* | stacking order (z-index): sticky headers, overlay popovers and dialogs |
--ct-text-* | text styles: heading, body, label, label-lg, value, caption; long-form pages: display, title, subtitle, prose, code; site pages: hero, lead, nav |
--ct-motion-* | transitions: feedback, enter, exit |
Tokens lists every one of them with its value in both themes and a sample.
Rules
- Typography.
--ct-text-<style>is afontshorthand.letter-spacingis not part of the shorthand: pair it with--ct-text-<style>-letter-spacing. Single values are available too:-font-family,-font-size,-font-weight,-line-height. - Motion.
--ct-motion-<name>is a duration, easing and delay:transition: background-color var(--ct-motion-feedback).exitis shorter thanenter, for things that close. - Shadows and layers.
--ct-shadow-<name>is abox-shadowvalue, stronger in the dark theme. It is decoration: whatever casts it also has a border and araisedbackground.--ct-layer-<name>is a plain number forz-index. - Code. The syntax colors follow the variables of Shiki's
css-variablestheme:--shiki-token-<name>takes--ct-color-syntax-<name>. Code blocks sit onbg-inset. - Contrast. Text roles meet WCAG targets on the backgrounds they are meant for, in both themes:
fg-defaultoncanvas,surfaceandraised(7:1),fg-mutedonsurfaceandraised(4.5:1), every syntax color oninset(4.5:1).fg-subtle(3:1) is for units and tick labels, not body text;fg-disabledis for disabled labels and has no target. - Never use
--ct-base-*. Those are the raw palette and scales behind the roles. They exist so roles can refer to them, are not part of the API, and change without notice.
In TypeScript
vars from @contour-ui/tokens holds a typed var(…) reference for every semantic token, for
inline styles and CSS-in-JS:
import { vars } from '@contour-ui/tokens';
<div style={{ padding: vars.space['panel-inset'], background: vars.color.bg.surface }} />;
// vars.color.bg.surface === 'var(--ct-color-bg-surface)'