Color Theory
Sveltopia Colors is built on three foundations: the OKLCH color space, the Radix 12-step scale system, and APCA contrast validation. Here's why each matters:
Why OKLCH?
Most color tools use HSL or RGB. These color spaces have a fundamental problem: they aren't perceptually uniform. A yellow and a blue at the same HSL lightness value look completely different in brightness to the human eye.
OKLCH (Oklab Lightness, Chroma, Hue) solves this. It's a perceptual color space where:
- L (Lightness) — 0 to 1. Two colors with the same L value actually look equally bright.
- C (Chroma) — 0 to ~0.4. How vivid the color is. Zero is gray.
- H (Hue) — 0 to 360 degrees. The color wheel position.
Practical impact: When you generate a 12-step scale in OKLCH, step 5 of every hue (orange, blue, green) looks the same brightness. In HSL, they'd look wildly different. This is what makes the scales feel consistent across your whole palette.
OKLCH also supports the P3 wide-gamut color space, giving you access to more vivid colors on modern displays. The generated CSS includes P3 fallbacks automatically.
The Radix 12-step scale
Radix Colors pioneered a 12-step scale where each step has a specific semantic purpose. Sveltopia Colors follows this system, mapped to a 50–950 naming convention for Tailwind compatibility:
| Step | Tailwind | Purpose | Example use |
|---|---|---|---|
| 1 | 50 | App background | Page body, full-bleed background |
| 2 | 100 | Subtle background | Sidebar, striped table rows |
| 3 | 200 | Component background | Card, input field, code block background |
| 4 | 300 | Hovered component | Card hover, button hover background |
| 5 | 400 | Active/selected | Active tab, selected list item |
| 6 | 500 | Subtle borders | Dividers, card borders |
| 7 | 600 | Strong borders | Input borders, focus rings |
| 8 | 700 | Hovered borders | Focus rings, hovered input borders |
| 9 | 800 | Primary solid | Buttons, links, primary actions |
| 10 | 850* | Hovered solid | Button hover states |
| 11 | 900 | Low-contrast text | Secondary text, labels, captions |
| 12 | 950 | High-contrast text | Headings, primary body text |
*850 is not part of Tailwind's default color scale. We added it to preserve the full Radix 12-step system — without it, the "hovered solid" state (step 10) would have no Tailwind equivalent.
Why 12 steps? Fewer steps (like 5 or 7) don't provide enough granularity for real UI work — you end up needing "between" values. More than 12 creates decision paralysis without meaningful perceptual differences. Twelve is the sweet spot that covers every UI need with clear semantic purpose for each step.
APCA contrast
WCAG 2.x uses a contrast ratio (like 4.5:1) that has known problems. It rates some readable combinations as failing and some hard-to-read combinations as passing, especially with colored text.
APCA (Accessible Perceptual Contrast Algorithm) is the replacement being developed for WCAG 3. It accounts for:
- Polarity — dark text on light backgrounds is perceived differently than light text on dark backgrounds
- Font size and weight — larger, bolder text needs less contrast than small body text
- Perceptual lightness — uses the OKLCH model for accurate luminance calculations
Sveltopia Colors uses these APCA thresholds:
| Use case | APCA threshold | Typical context |
|---|---|---|
| Body text | 75 | 14–16px paragraph text |
| Large text / UI | 60 | 18px+ headings, buttons, icons |
| Decorative | 45 | Non-text elements, borders, dividers |
Critical text contrast pairs — steps 11–12 (text colors) against steps 1–2 (backgrounds) — are validated and auto-adjusted during generation to meet these thresholds. Button solid steps (9–10) are validated but flagged as warnings rather than auto-corrected, since saturated hues naturally have lower contrast that APCA accounts for through font size and weight. See the Accessibility page for safe combination guidelines.
Further reading
- oklch.com — interactive OKLCH color picker
- Radix Colors — the 12-step scale system we build on
- APCA Contrast — the accessible contrast algorithm