R RenDS v0.13.0

Foundations

Tokens

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.

The three tiers

Tokens cascade from raw values to concrete component settings. Consume the layer that matches your intent — never reach past it.

Tier 1 · Raw
Primitives

The physical palette. Colors, space scale, type scale, radius sizes. Do not use directly in components.

--blue-500
--space-4
--text-base
Tier 2 · Meaning
Semantic

Purpose-based. This is what most components consume. They switch light/dark via light-dark().

--color-accent
--color-text-muted
--color-surface
Tier 3 · Component
Component

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

How they flow

/* 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); }

Primitive colors

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.

Blue (accent default)

--blue-50#EFF6FF
--blue-100#DBEAFE
--blue-300#93C5FD
--blue-500#3B82F6
--blue-600#2563EB
--blue-700#1D4ED8
--blue-900#1E3A8A

Grayscale

--gray-50#FAFAFA
--gray-100#F5F5F5
--gray-200#E5E5E5
--gray-400#A3A3A3
--gray-500#737373
--gray-600#525252
--gray-700#404040
--gray-900#171717

Also shipped

Full scales for: --red-*, --orange-*, --yellow-*, --green-*, --teal-*, --indigo-*, --purple-*, --pink-*, --slate-*. Plus --black and --white.

Semantic colors

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.

Content

TokenRolePreview
--color-textBody text, headingsThe quick brown fox
--color-text-secondarySupporting proseThe quick brown fox
--color-text-mutedLabels, captions, helperThe quick brown fox
--color-text-faintIncidental only — disabled labels, decorative punctuation. Not AA — never use for text that must be read.(preview omitted — would fail contrast by design)
--color-text-linkLinksThe 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.

DO / DON'T: --color-text-faint in the wild

Two columns, real previews. Same token, very different fitness depending on what the user has to read.

Do — incidental

DocsComponentsButton

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

Pro plan — $24/mo

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 Email
Anaana@example.com

Table headers tell the user what each column is. Use --color-text-muted; --color-text-faint fails AA here.

1 Account 2 Profile

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.

Non-text swatch: the faint token itself.

Show the token as a swatch when documenting it; avoid using the token on readable text in the docs.

Surfaces

TokenRolePreview
--color-surfacePage background
--color-surface-raisedCards, sheets
--color-surface-sunkenInputs, code blocks
--color-surface-overlayModals, popovers

Intent (status colors)

TokenRolePreview
--color-accentBrand, primary actions
--color-successConfirmations, positives
--color-warningCautions, pending
--color-dangerErrors, destructive
--color-infoNeutral messages

Borders & fills

TokenRolePreview
--color-borderDefault edges
--color-border-strongEmphasis edges
--color-border-mutedDividers, faint edges
--color-fillHover surfaces
--color-focus-ringKeyboard focus

Spacing

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.

How to read the name

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.

--space-11 × 4px = 4px
--space-22 × 4px = 8px
--space-44 × 4px = 16px

Choose by relationship

Closely related4px

--space-field-helper-gap between an input and its helper text.

Inline group8px

--space-inline-tight between an icon and its label.

Component interior24px

--space-card-padding gives content room inside a surface.

Separate sections48px

--space-section separates major content groups.

Primitive scale

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-2496px

Exact 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.

Type scale

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

Weights

--weight-regular, --weight-medium, --weight-semibold, --weight-bold. Plus --font-sans, --font-mono, --font-serif.

Radius

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

Component tokens

Each 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.

Example: Button

/* 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); }

Overriding

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 */ }

Live preview

Every preview above is a real <button class="ren-btn ren-btn-primary"> — the only differences are inline token overrides on the element.

Override patterns

Three patterns, in order of recommended reach:

1. Override semantic tokens (widest reach)

Change the meaning of "accent" everywhere, instantly:

:root { --color-accent: #8B5CF6; /* violet brand */ --color-accent-hover: #7C3AED; }

2. Override component tokens (component-wide)

Change how all buttons look without touching button CSS:

:root { --ren-btn-radius: var(--radius-full); --ren-btn-padding-x: var(--space-5); }

3. Scope to a theme class (opt-in)

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