The Complete Guide to Color Palettes 2026

Updated 2026-03-27 · By SPUNK13 · Back to spunk.bet

A palette for an interface is a different job from a palette for a poster. You are not picking five colours you like, you are building a system where every shade has a defined role, passes a contrast threshold, and still works when the whole thing flips to dark mode. The colour space you build it in decides how much of that comes out right by default, which is why the first section is about OKLCH rather than about hue harmonies. The rest covers ramps, contrast, proportion, naming, and the tools that remove the arithmetic.

Why OKLCH replaced HSL for building ramps

HSL lies about lightness. hsl(60 100% 50%) is yellow and hsl(240 100% 50%) is blue, and both claim 50% lightness, but the yellow is blindingly bright and the blue is nearly black. That is why an HSL-generated palette gives you a yellow that fails contrast on white and a blue that is unreadable on dark.

OKLCH is built on a perceptual colour space, so the lightness number matches what the eye reports. Every hue at 70% looks about equally bright. The syntax is lightness, chroma, hue: oklch(70% 0.15 250). Chroma is saturation but unbounded — around 0.37 is the practical ceiling, and values past a hue's gamut limit get clipped, which is where an over-saturated ramp suddenly stops getting more vivid. Browser support has been broad across Chrome, Safari and Firefox for a while now; if you still need a fallback, declare a hex value on the line above and let the cascade handle it.

Building a 50 to 950 ramp

Pick your brand colour, read its OKLCH values, then hold hue roughly constant and step lightness evenly. Eleven steps is the convention because it maps to the 50/100/200…900/950 naming everyone now recognises.

--brand-50:  oklch(97% 0.02 250);
--brand-100: oklch(94% 0.04 250);
--brand-300: oklch(82% 0.10 250);
--brand-500: oklch(62% 0.16 250);  /* the brand colour */
--brand-700: oklch(45% 0.14 250);
--brand-900: oklch(28% 0.09 250);

Two refinements make it look designed rather than generated. Drop chroma at both ends — a near-white with full chroma looks like a printing error, and a near-black with full chroma looks muddy. And shift hue slightly as you darken, a few degrees toward blue for cool colours, which is how pigment actually behaves in shadow.

The contrast numbers you have to hit

WCAG 2 asks for 4.5:1 between body text and its background, 3:1 for large text (18pt, or 14pt bold) and for the boundaries of interactive components — button borders, form field outlines, focus rings. AA is the level almost every procurement and accessibility policy references, so treat 4.5:1 as the floor rather than the goal.

The practical consequence for a ramp: on a white surface, the first step that passes 4.5:1 is usually somewhere around the 600 or 700 stop, which means your 500 brand colour is often not usable for text on white even though it is the colour on the logo. That is normal. Use 500 for fills, 700 for text. Check it in browser devtools, which shows the ratio inline in the colour picker and draws a line on the picker showing where the passing region starts.

How much of each colour to use

The 60-30-10 split is the fastest way to stop a design looking chaotic: roughly 60% a neutral surface, 30% a secondary neutral or muted tone, 10% the accent. In UI terms that means most of the screen is background and cards, a smaller share is borders and muted text, and the brand colour appears on the primary button and almost nothing else.

One accent is enough. A second accent needs a job — commonly a semantic set for success, warning and error, which are not really brand colours and should be chosen for legibility rather than personality. Green and red used as the only signal excludes people with the most common forms of colour blindness, so pair every colour-coded state with an icon or a word.

Name colours by role, not by value

Referring to --blue-600 in a component means a rebrand touches 300 files. Add a semantic layer on top of the ramp:

--surface:      var(--neutral-50);
--surface-raised: white;
--text:         var(--neutral-900);
--text-muted:   var(--neutral-600);
--border:       var(--neutral-200);
--accent:       var(--brand-600);

Components only ever reference the semantic names. Theming then becomes one block that reassigns those six variables.

Dark mode is not an inversion

Flipping the ramp gives you the two classic mistakes: pure white text on pure black, which produces halation and makes text vibrate for readers with astigmatism, and saturated colours that glow. Use something around oklch(18% 0.01 250) for the background and oklch(92% 0 0) for text rather than #000 and #fff, and reduce chroma on every accent by roughly a third.

Elevation also inverts: in light mode a raised card casts a shadow, in dark mode shadows are invisible so raised surfaces get lighter instead. And check contrast separately in both themes — a colour that passes on white frequently fails on dark grey, because the ratio depends on both ends.

Tools that do the tedious part

Radix Colors ships twelve-step scales built with the steps already assigned to jobs (step 9 is the solid brand fill, step 11 is accessible text), which removes most of the guesswork. Tailwind's default palette is a well-tuned reference even if you never use the framework. Coolors is fast for generating a starting harmony, though you should still rebuild the ramp in OKLCH afterwards. For checking, browser devtools handles per-element ratios and Chrome can simulate protanopia, deuteranopia and tritanopia directly in the Rendering panel — five minutes there catches problems that no ratio number reports. Terminology worth knowing before you start is collected in the colour palette glossary.

Want More?

Explore our complete collection of free tools and resources.

Visit spunk.bet
400+ Free Tools Free Casino Meme Generator Birth Charts Scam Database BPM Calculator