4px--space-field-helper-gap between an input and its helper text.
Foundations
Every visual decision in RenDS comes from a token. Three layers — primitives, semantic, component — each with a job. Understanding this separation is the single biggest unlock for customizing the system.
Tokens cascade from raw values to concrete component settings. Consume the layer that matches your intent — never reach past it.
The physical palette. Colors, space scale, type scale, radius sizes. Do not use directly in components.
--blue-500
--space-4
--text-base
Purpose-based. This is what most components consume. They switch light/dark via light-dark().
--color-accent
--color-text-muted
--color-surface
Per-component override hooks. Default to semantic tokens. This is how you theme without fighting specificity.
--ren-btn-bg
--ren-card-radius
--ren-field-border-color
/* Tier 1: define the raw value */
--blue-500: #007AFF;
/* Tier 2: assign meaning */
--color-accent: light-dark(var(--blue-500), var(--blue-400));
/* Tier 3: wire to a component */
--ren-btn-bg: var(--color-accent);
/* Component rule consumes the component token */
.ren-btn-primary { background: var(--ren-btn-bg); }RenDS ships 11 color scales with 11 steps each (50 → 1000). These are the raw palette. You'll usually reference them through semantic tokens, but they're available when you need them.
Full scales for: --red-*, --orange-*, --yellow-*, --green-*, --teal-*, --indigo-*, --purple-*, --pink-*, --slate-*. Plus --black and --white.
These are the tokens you'll actually use 95% of the time. Each has a purpose, not a shade. Swapping light/dark mode swaps their underlying values — you write the same CSS either way.
| Token | Role | Preview |
|---|---|---|
--color-text | Body text, headings | The quick brown fox |
--color-text-secondary | Supporting prose | The quick brown fox |
--color-text-muted | Labels, captions, helper | The quick brown fox |
--color-text-faint | Incidental only — disabled labels, decorative punctuation. Not AA — never use for text that must be read. | (preview omitted — would fail contrast by design) |
--color-text-link | Links | The quick brown fox |
Policy: --color-text-faint is incidental, not "text-muted-lite"
Use it only for content WCAG 2.1 SC 1.4.3 explicitly exempts: disabled labels (paired with opacity < 1), placeholder hints, decorative punctuation (breadcrumb separators, ellipsis), calendar cells outside the current month.
Do not use it for eyebrows, kickers, captions, table headers, step numbering, help text, footnote references, tooltip content, or anything a user must read to understand the UI. Use --color-text-muted for those — it's the darkest "secondary" token that still clears AA on every surface.
Rule of thumb: if losing the text loses information, it can't be --color-text-faint. See Accessibility → Color contrast.
--color-text-faint in the wildTwo columns, real previews. Same token, very different fitness depending on what the user has to read.
Do — incidental
Breadcrumb separators (›) are decorative — the active item is the information.
Calendar days outside the current month are contextual. Keep visible numerals readable; reserve --color-text-faint for non-text decoration.
Disabled controls can use softer affordances, but docs still render visible labels at AA contrast.
Placeholder is a hint about what to type — never the only label.
Don't — must be read
Pricing
Eyebrow / kicker text — readers do rely on it to scan a section. This preview uses --color-text-muted; do not switch it to faint.
| Name | |
|---|---|
| Ana | ana@example.com |
Table headers tell the user what each column is. Use --color-text-muted; --color-text-faint fails AA here.
Step numbers in a wizard — the user reads them to orient. Use --color-text-muted or the accent for current step.
Your password must contain at least 8 characters, one number, and one symbol.
Help text under a field — if it's worth showing, it's worth reading. Use --color-text-muted.
Show the token as a swatch when documenting it; avoid using the token on readable text in the docs.
| Token | Role | Preview |
|---|---|---|
--color-surface | Page background | |
--color-surface-raised | Cards, sheets | |
--color-surface-sunken | Inputs, code blocks | |
--color-surface-overlay | Modals, popovers |
| Token | Role | Preview |
|---|---|---|
--color-accent | Brand, primary actions | |
--color-success | Confirmations, positives | |
--color-warning | Cautions, pending | |
--color-danger | Errors, destructive | |
--color-info | Neutral messages |
| Token | Role | Preview |
|---|---|---|
--color-border | Default edges | |
--color-border-strong | Emphasis edges | |
--color-border-muted | Dividers, faint edges | |
--color-fill | Hover surfaces | |
--color-focus-ring | Keyboard focus |
Spacing tokens are reusable distances for gap, padding, margins, and positioning. The system is based on an 8px grid, while 4px half-steps handle tighter relationships.
The suffix is a scale step, not a pixel value. For the common numeric steps, multiply the suffix by 4px: --space-2 means 2 × 4px = 8px.
4px--space-field-helper-gap between an input and its helper text.
8px--space-inline-tight between an icon and its label.
24px--space-card-padding gives content room inside a surface.
48px--space-section separates major content groups.
These are the most frequently used raw steps. The bars use the tokens themselves, so their rendered length is the documented value.
--space-14px--space-28px--space-312px--space-416px--space-520px--space-624px--space-832px--space-1040px--space-1248px--space-1664px--space-2496pxExact range: --space-0, --space-px, --space-0-25, --space-0-5, --space-1 through --space-10, then --space-12, --space-14, --space-16, --space-20, --space-24, --space-32, --space-40, --space-48, --space-56, and --space-64.
Fractional tokens: only --space-0-25 (1px), --space-0-5 (2px), and --space-1-5 (6px) exist. There are no other fractional names in the API.
For everyday layouts, prefer semantic names such as --space-card-padding, --space-form-gap, or --space-section. They describe intent and adapt to density settings.
Apple-HIG-inspired scale with --text-base (16px) as the anchor. All sizes are in rem so the user's browser font preference is respected.
--text-xs11pxThe quick brown fox jumps over the lazy dog--text-sm13pxThe quick brown fox jumps over the lazy dog--text-base16pxThe quick brown fox jumps over the lazy dog--text-md17pxThe quick brown fox jumps over--text-lg18pxThe quick brown fox jumps over--text-xl20pxThe quick brown fox jumps over--text-2xl22pxThe quick brown fox--text-3xl26pxThe quick brown fox--text-4xl28pxThe quick brown--text-5xl34pxThe quick brown--text-6xl40pxThe quick--weight-regular, --weight-medium, --weight-semibold, --weight-bold. Plus --font-sans, --font-mono, --font-serif.
Seven steps from --radius-none to --radius-full. Card components default to --radius-lg, inputs to --radius-md, pills to --radius-full.
--radius-xs2px--radius-sm4px--radius-md8px--radius-lg12px--radius-xl16px--radius-2xl20px--radius-fullpillEach component exposes --ren-<component>-* custom properties. These are the theming hooks. You never need to fight specificity or use !important — just override the hook on any ancestor.
/* Defaults (in tokens/component/tokens.css) */
:where(:root, [data-theme]) {
--ren-btn-bg: var(--color-accent);
--ren-btn-bg-hover: var(--color-accent-hover);
--ren-btn-bg-active: var(--color-accent-active);
--ren-btn-color: var(--color-on-accent);
--ren-btn-border-color: var(--color-accent);
--ren-btn-radius: var(--radius-md);
--ren-btn-padding-x: var(--space-4);
--ren-btn-padding-y: var(--space-2);
}
.ren-btn-primary {
background-color: var(--ren-btn-bg);
color: var(--ren-btn-color);
border-color: var(--ren-btn-border-color);
}Scoped to a theme class:
.brand-ai {
--ren-btn-bg: var(--color-ai);
--ren-btn-bg-hover: var(--color-ai-hover);
--ren-btn-bg-active: color-mix(in oklch, var(--color-ai), var(--color-ai-hover) 50%);
--ren-btn-color: var(--color-on-ai);
--ren-btn-border-color: var(--color-ai);
}Or globally on :root:
:root {
--ren-btn-radius: var(--radius-full); /* all buttons pill-shaped */
}Every preview above is a real <button class="ren-btn ren-btn-primary"> — the only differences are inline token overrides on the element.
Three patterns, in order of recommended reach:
Change the meaning of "accent" everywhere, instantly:
:root {
--color-accent: #8B5CF6; /* violet brand */
--color-accent-hover: #7C3AED;
}Change how all buttons look without touching button CSS:
:root {
--ren-btn-radius: var(--radius-full);
--ren-btn-padding-x: var(--space-5);
}<section class="brand-rose">
<button class="ren-btn ren-btn-primary">Only rose here</button>
</section>
<style>
.brand-rose {
--color-accent: #F43F5E;
--color-accent-hover: #E11D48;
}
</style>For a visual way to explore overrides, open the Theme Builder and play with the sidebar pickers.