Colors v0.1.0

Accessibility

Sveltopia Colors uses the Radix 12-step scale system, where each step has a semantic purpose that guides accessible color usage. Critical text/background pairings are APCA-validated and auto-corrected during generation.

Step semantics

Each step in the 12-step scale has a designed purpose and an accessibility guideline. Using steps for their intended purpose ensures accessible results:

StepsTailwindPurposeAccessibility guideline
1–250–100BackgroundsSafe as backgrounds for steps 11–12 text *
3–5200–400Interactive surfacesSafe as backgrounds for step 12 text (by design)
6–7500–600BordersSufficient contrast against backgrounds (by design)
8–10700–850Solid fillsWhite/contrast text at large font sizes **
11–12900–950TextAccessible on background steps 1–2 *

* Enforced: the generator auto-corrects steps 11–12 against steps 1–2 if contrast falls below APCA thresholds.
** Validated as warning: step 9 is checked for white text readability but not auto-corrected, since saturated hues (yellow, lime) intentionally use dark text instead.

Guidelines marked "by design" follow from the Radix step system's lightness progression — each step group is spaced to maintain perceptual contrast with its intended pairings.

Safe combinations

These pairings are designed to meet APCA contrast thresholds:

Text on backgrounds — Steps 11–12 text on steps 1–2 backgrounds (enforced) (e.g., text-orange-950 on bg-orange-50)
Text on surfaces — Step 12 text on steps 3–5 backgrounds (e.g., text-blue-950 on bg-blue-200)
White text on solids — White text on step 9 at large font sizes (validated as warning) (e.g., white text on bg-orange-800 — some saturated hues may need dark text instead)
Borders on backgrounds — Steps 6–7 borders on steps 1–2 backgrounds (by design) (e.g., border-blue-600 on bg-blue-50)

Avoid these pairings

Adjacent steps as text/background — Steps that are close in lightness (e.g., step 6 text on step 5 background)
Light text on light backgrounds — Steps 1–5 text on steps 1–5 backgrounds
Dark text on dark backgrounds — Steps 8–12 text on steps 8–12 backgrounds

The palette gives you the building blocks for accessible interfaces — but which combinations you use is up to you. Follow the step semantics above and you'll produce accessible results without needing to think about contrast ratios.

Why APCA over WCAG 2.x?

WCAG 2.x's contrast ratio algorithm has known issues:

  • It treats dark-on-light and light-on-dark identically, but human perception doesn't
  • It doesn't account for font size — a huge heading needs less contrast than 12px body text
  • It produces false positives (passing combinations that are hard to read) and false negatives (failing combinations that are perfectly readable)

APCA (Accessible Perceptual Contrast Algorithm) addresses all of these. It's being developed for WCAG 3 and produces results that better match how humans actually perceive contrast.

A concrete example: white text on a saturated orange button (step 800) fails WCAG 2.x with a ratio of 2.87:1 — well below the 4.5:1 threshold. But the same pairing passes APCA at every practical button font size, because APCA accounts for the fact that large, bold text on a vivid background is perfectly readable. This is the kind of false negative that makes WCAG 2.x frustrating for design systems with saturated brand colors.

How validation works

During palette generation, the generator validates and enforces contrast for the most critical pairings:

  1. Text steps (11–12) are checked against background steps (1–2). Step 12 must meet body text contrast (APCA ≥ 75); step 11 must meet large text contrast (APCA ≥ 60). If either falls short, the generator automatically adjusts lightness until the threshold is met.
  2. Solid fill step (9) is checked for white text readability (APCA ≥ 60). This is flagged as a warning rather than auto-corrected, since some saturated hues (yellow, lime) intentionally use dark text on solid backgrounds.

Other step relationships (borders against backgrounds, text on interactive surfaces) follow from the Radix scale's perceptual lightness curve — each step group is spaced to maintain usable contrast with its intended pairings. You can verify any palette using the programmatic API below.

Automatic remediation: You don't need to manually check text contrast. If a brand color would produce text steps (11–12) that fall below APCA thresholds against background steps (1–2), the generator automatically adjusts lightness while preserving hue and chroma as closely as possible.

Testing your implementation

Use the programmatic API to validate palettes in your CI pipeline:

typescript
import { generatePalette, validatePaletteContrast, formatContrastReport } from '@sveltopia/colors';

// generatePalette returns a single mode — generate both
const light = generatePalette({ brandColors: ['#FF4F00'] });
const dark = generatePalette({ brandColors: ['#FF4F00'], mode: 'dark' });

const palette = {
  light: light.scales,
  dark: dark.scales,
  _meta: {
    tuningProfile: light.meta.tuningProfile,
    inputColors: ['#FF4F00'],
    generatedAt: new Date().toISOString()
  }
};

const report = validatePaletteContrast(palette);

if (!report.passed) {
  console.error(formatContrastReport(report));
  process.exit(1);
}

console.log(`All ${report.totalChecks} checks passed.`);