OKLCH Theme & Palette Generator

Generate harmonic color palettes and accessible design tokens in OKLCH perceptual space. Engineered with WCAG AAA contrast validation for Dark/Light modes and direct export to CSS and Tailwind CSS v4.

Harmonic Palette (5 hues)

Click any swatch to copy HEX

Design Tokens (Dark)

Click to copy HEX
--primary#236BDD
--secondary#E3BE80
--accent#426EB4
--muted#12161C
--background#030407

Interface Preview

#004FBE
#3B82F6
#84B3FF
#F7D193
#B99964
B
Buscer SaaS Dashboard
Revenue+12.5%
$48,290

vs. último periodo

LatenciaAtención
142ms

P95 · últimas 24h

ErroresCrítico
3

últimas 4 horas

🛡️WCAG 2.1 AA Verified: Min interactive 4.76:1 (≥4.5:1) · Micro-typography 9.14:1 (AAA)
AA PASSOKLCH

Export Code

:root {
  --background: #F7FAFF;
  --foreground: #051127;
  --primary: #2068D9;
  --primary-foreground: #F7FAFF;
  --primary-text: #10397B;
  --secondary: #F7D193;
  --secondary-foreground: #261E10;
  --secondary-text: #523800;
  --accent: #426EB4;
  --accent-foreground: #F7FAFF;
  --accent-text: #0F3A7B;
  --muted: #E6ECF5;
  --muted-foreground: #393D44;
  --card: #FFFFFF;
  --card-foreground: #041127;
  --border: #D5DBE5;
  --ring: #2068D9;
  --input: #EAEFF7;
  --destructive: #9D2E56;
  --destructive-foreground: #FFF8F9;
  --destructive-text: #6C1737;
  --success: #006646;
  --success-foreground: #F3FDF8;
  --success-text: #004931;
  --warning: #9B3B00;
  --warning-foreground: #FFF9F6;
  --warning-text: #682500;
}

.dark {
  --background: #030407;
  --foreground: #F8FAFF;
  --primary: #236BDD;
  --primary-foreground: #F7FAFF;
  --primary-text: #83B2FD;
  --secondary: #E3BE80;
  --secondary-foreground: #261E10;
  --secondary-text: #E3BE80;
  --accent: #426EB4;
  --accent-foreground: #F7FAFF;
  --accent-text: #83B2FD;
  --muted: #12161C;
  --muted-foreground: #A0A5AC;
  --card: #0A0B0E;
  --card-foreground: #F8FAFF;
  --border: #1B1F27;
  --ring: #236BDD;
  --input: #111419;
  --destructive: #AD4568;
  --destructive-foreground: #FFF8FA;
  --destructive-text: #F090AB;
  --success: #007C56;
  --success-foreground: #F3FDF8;
  --success-text: #66C79D;
  --warning: #AF4E1E;
  --warning-foreground: #FFF9F6;
  --warning-text: #F19771;
}
Behind the Build

How I Built an Accessible Theme Generator with OKLCH and Tailwind v4

The technical choices behind the tool: perceptual color spaces, gamut mapping, and modern CSS contrast.

Buscer

Julio Buscer

Engineering · AI-assisted

Full Stack & Software Architecture

4 min read

“I built this generator because I got tired of the same old issues: secondary buttons outshining primary actions, unreadable text on dark cards, and colors that break when moving them into CSS. Here is how I solved it and why I made each choice.”

What does it do?

Generates complete light and dark palettes with accessible contrast, ready to copy and paste into Tailwind v4 (@theme) or CSS custom properties. Everything runs in your browser with zero heavy libraries.

Why was it needed?

Traditional color models like HSL treat lightness as a linear mathematical scale without accounting for how human vision perceives brightness. This often results in light text losing legibility over bright yellows or greens, or dark themes with excessive saturation that cause eye strain.

4 core choices when building the tool

Decisión 01Color space

Switching HSL to OKLCH

The problem

In HSL, 50% yellow and 50% blue share the same number, but human eyes see yellow as far brighter. If you put white text on that yellow, it is completely unreadable.

The solution

We switched to OKLCH. In this space, lightness (L) matches real human vision: 70% brightness looks equally clear on blue or yellow.

Practical rule:Use OKLCH if you want button contrast to work reliably without relying on luck.
Ottosson, B. (2020). A perceptual color space for computer graphics.
View source
Decisión 02Display boundaries

Adjusting out-of-gamut colors instead of clipping them

The problem

Computing colors in OKLCH can yield shades normal screens cannot show. If you let the browser truncate RGB channels, the hue gets distorted and purple turns into a dull blue.

The solution

We added a fast binary search: lock the hue and lightness, and gently dial down chroma until the color fits on screen without losing its identity.

Practical rule:If a color exceeds the screen limit, lower its saturation; do not let the browser clip it blindly.
W3C (2022). CSS Color Module Level 4: Gamut Mapping Algorithm.
View source
Decisión 03Typography

Splitting button color from text color

The problem

A medium-brightness color works well on a large button, but using that same color on small text over a dark surface makes letters hard to read.

The solution

We created dedicated text tokens (*-text). They brighten the text enough to pass AAA contrast (6.5:1 or higher) without changing the button color.

Practical rule:A button surface has plenty of area; fine typography needs much more lightness contrast.
W3C WCAG 2.1 — Success Criterion 1.4.6 Contrast (Enhanced).
View source
Decisión 04Visual hierarchy & spectrum

Spectral natural lightness curve & button hierarchy

The problem

Rotating 180° for complements while forcing the base color's lightness causes yellows and warm hues to turn into muddy brown or dirty mustard. Furthermore, if secondary buttons lack visual presence or rely on guesswork text colors, the interface breaks down and fails accessibility.

The solution

We modeled the human cone natural lightness curve (Helmholtz-Kohlrausch) in OKLCH: warm complements modulate to their natural peak (L ≈ 0.82–0.88), map directly with genuine presence to the secondary button with guaranteed AAA contrast (>11:1), and the engine audits every interactive ratio in real time.

Practical rule:Every hue has a natural brightness in human vision; never force yellow to match the lightness of blue.
Vos, J. J. (1960). Some new aspects of chromostereopsis. JOSA.
View source

The 6 harmonies built into the generator

Complementary

180°

Opposite hues modulated with their spectral natural lightness to avoid muddy tones.

Triadic

120° / 240°

Three equidistant points around the wheel for balanced, varied UIs.

Analogous

+15° / +30°

Neighboring hues for calm, cohesive interfaces.

Split Complementary

150° / 210°

Base color plus two adjacent to its opposite. High contrast without visual clash.

Tetradic

60° / 180° / 240°

Two complementary pairs for when you need multiple tags or state indicators.

Monochromatic

0° (ΔL, ΔC)

Same hue varying only lightness and saturation. Great for minimal UIs.

4 practical takeaways for any project

1

Avoid pure black (#000000) in dark mode: vivid colors on black make text feel like it vibrates; use deep dark grays.

2

Respect the natural lightness of each hue: yellow requires L > 0.80 to avoid looking like mustard or mud.

3

Guide attention with brightness and contrast: the secondary button needs harmonic presence while strictly passing WCAG AA.

4

Use native CSS: Tailwind v4 and CSS variables handle all this in the browser without extra libraries.

Using it with Tailwind CSS v4

Paste the @theme block into your CSS and you are good to go. Light and dark modes work with native variables, no extra setup required.

Technology Stack

The tool's chromatic engine runs on 0 npm dependencies. I chose to implement the mathematical formulas directly in vanilla TypeScript to keep an ultra-lightweight bundle (0 KB added) and retain full control over every calculation based on open standards, though great libraries exist across the ecosystem.

OKLab / OKLCH Matrix Transformations

Code

Perceptual space with zero hue shift

For this generator, I implemented Björn Ottosson's spectral matrices directly in ~40 lines to operate natively in OKLCH with zero hue shift and no bundle overhead. If you prefer off-the-shelf packages, established libraries like chroma-js or colord are popular choices for general color handling.

Binary Gamut Mapping (W3C CSS Color 4)

Code

Out-of-gamut adaptation for sRGB

For our specific use case, implementing the official W3C binary search algorithm in just 30 lines allowed us to map out-of-gamut colors while strictly preserving exact hue and lightness. For broader application needs, comprehensive libraries like colorjs.io provide robust implementations of modern color specs.

Spectral Natural Lightness Curve

Code

Psychophysical hue compensation (Helmholtz-Kohlrausch)

To prevent warm complements (such as yellow or lime) from collapsing into mustard or mud tones when rotated from blue, I formulated getNaturalLightness() and adjustLightnessForHue() in ~20 zero-dependency lines. It modulates lightness toward each hue's perceptual peak, producing radiant gold tones and WCAG AAA contrast.

Universal Harmonic Tinting & Dynamic WCAG Audit

Code

Suppression of visual vibration & real-time ergonomic contrast

To eradicate dirty visual vibration and muddy flat black text on interactive buttons in light mode, I implemented an engine that mandates light text on buttons with L < 0.65 and applies universal harmonic tinting in each button's hue, validated with a live dynamic contrast audit rather than static notes. For standard mathematical checks, polished or generic a11y packages are common alternatives.

Frequently Asked Questions

Q:How do I share a palette with my team?

Simply copy your browser URL or click 'Share palette'. Your base color, harmony, and mode sync instantly into query parameters without refreshing the page, so your team opens the exact same palette.

Q:Why does a blue's complement look golden or clean yellow rather than dirty mustard?

Because we apply a natural lightness curve. The human eye naturally perceives yellow with significantly higher luminance. Forcing the lower lightness of a blue onto yellow causes our brain to interpret it as mud or mustard.

Q:Can I import these tokens into Figma or Tokens Studio?

Yes. Under the 'Tokens W3C (JSON)' tab, copy the file and import it directly into the Tokens Studio for Figma plugin, or compile it with Style Dictionary under official W3C specifications.

Q:What is the difference between OKLCH and HSL?

HSL computes brightness purely through math without accounting for human eyes. OKLCH tunes the numbers to match what we actually see: if two colors share the same lightness in OKLCH, they look equally bright.

Q:Why are there dedicated tokens like --primary-text?

Because a color that works great as a button background becomes unreadable on small text against dark cards. The *-text tokens brighten the letters so they read easily and pass accessibility checks.

Q:How do I use these colors in my project?

Paste the @theme block into your globals.css. Tailwind v4 generates utility classes like bg-primary or text-primary-text, and toggling the .dark class switches values automatically.

Q:What happens if I pick a very saturated color?

The tool gently lowers saturation step by step until your screen can display it, keeping the exact hue without distorting it.

Q:How is the viewer's WCAG accessibility badge calculated?

It is audited dynamically in real time: we calculate the mathematical contrast of every interactive and typographic pair on screen, showing the certified live grade (AA PASS or AAA PASS) without canned or hardcoded claims.

Q:Do I need to install anything to use these colors?

Nothing at all. Everything works with standard browser CSS and Tailwind v4.

Found this Behind the Build useful?

Share it with your team or network to foster accessible interface design with OKLCH.