Design Cluster
Color Theory for Designers: Practical Palettes and Contrast
Color is the design decision most likely to be made badly by people who are otherwise careful. Designers pick two colors they like, add a third because the layout needed an accent, and ship a palette that fails WCAG contrast at the hover state, looks identical to red-green color blind users, and breaks entirely in dark mode. Nobody set out to make a bad palette. They just did not apply the theory that would have caught the problem before it shipped.
The theory is not hard. This post walks through the color models worth knowing, how to construct a palette that actually works, how to check contrast against the WCAG bar without guessing, and how to test for color blindness before your users find the issue. The goal is a working designer's toolkit, not an art-school course.
Color models: HSL, RGB, OKLCH
Three color models cover almost everything you will do on the web.
RGB (and hex)
RGB is the hardware model — red, green, and blue channels combine to produce a color. Hex codes (#ff6b6b) are compact RGB. They are what browsers consume natively, but they are terrible for reasoning about color because "#ff6b6b and #feca57 with equal saturation" is not a thing you can see by looking at the numbers. RGB is for serialization, not for thinking.
HSL
HSL separates color into hue (position on the color wheel, 0-360), saturation (how intense, 0-100%), and lightness (how bright, 0-100%). HSL is dramatically easier to reason about than RGB. "The same color but lighter" is just increasing the lightness channel. "A matching accent" is shifting the hue by 30 degrees and keeping saturation and lightness constant.
HSL has one well-known flaw: it is not perceptually uniform. Two colors with the same lightness value can look meaningfully different in actual brightness to the human eye. Yellow at 50% lightness looks brighter than blue at 50% lightness. This is a problem for color systems that depend on consistent brightness.
OKLCH
OKLCH — defined in CSS Color Module Level 4 — is the modern answer. Lightness is perceptually uniform (50% lightness looks equally bright regardless of hue). Chroma replaces saturation with a more meaningful "how colorful" dimension. Hue is the same 0-360 wheel. Palettes built in OKLCH are more consistent across hues than palettes built in HSL, which is why modern design systems are migrating to it.
Convert between all of these with Color Converter, which handles hex, RGB, HSL, HSV, CMYK, and OKLCH. Pick starting colors with Color Picker, which gives you live previews across every representation. The CIE color science behind OKLCH is documented at the International Commission on Illumination, though the practical details are easier in the W3C spec.
Building a palette that works
A working web palette has roughly these roles:
- One primary brand color — the thing people identify with the brand.
- A primary scale — the primary color in 9 or 11 lightness steps, from near-white to near-black.
- One or two accent colors — for links, buttons, highlights. Usually complementary or triadic to the primary.
- A neutral scale — grays (or tinted grays) for backgrounds, borders, and text.
- Semantic colors — green for success, red for error, amber for warning, blue for info.
The trick to making the scale consistent is to build it in OKLCH, fix the chroma and hue at your primary color, and vary only the lightness. That gives you nine variants of the same color that are perceptually evenly spaced. The same approach produces neutral grays that harmonize with the primary — start with your primary and drop the chroma to near-zero, then vary lightness.
For semantic colors, the Material Design 3 color system documents a mature approach with tonal palettes and role-based tokens that scales from light to dark mode without breaking. Color Palette Generator handles the construction side — feed it a brand color and it produces the primary scale, complementary accents, and a neutral scale in one pass.
WCAG contrast, in practice
WCAG 2.1 defines contrast ratios for text against backgrounds. The key numbers: 4.5:1 for normal text at AA level, 3:1 for large text at AA, 7:1 for normal text at AAA. A ratio of 1:1 means identical colors (invisible). A ratio of 21:1 is pure white on pure black. Real palettes live in the middle.
The practical check is to run every text-on-background combination in your design through a contrast checker. Headings, body text, links, buttons, placeholder text, disabled states, focus outlines — all of it. It sounds like a lot. It takes about ten minutes for a full check, and it catches the 30% of palettes that look fine on a designer's monitor and fail on a user's phone in sunlight. Use Contrast Ratio Checker to verify specific pairs, and plan to re-check any time you touch the palette.
A common failure mode: the primary button passes contrast at rest but fails on hover because the hover state is slightly lighter and the designer did not re-check. Always check every interactive state, not just the default. The WCAG 2.1 Quick Reference is the authoritative list of contrast requirements and exceptions.
Testing for color blindness
Roughly 8% of men and 0.5% of women have some form of color vision deficiency, most commonly red-green. Designs that rely on color alone to convey meaning — "click the red button" or "green means success" — fail for this audience. The fix is not to avoid color but to add redundant cues: icons, text labels, patterns, or position.
Three quick tests to run on any design:
- Grayscale test. Convert the design to grayscale and check that the hierarchy and affordances are still readable. If a red "Delete" button and a green "Save" button become the same gray, you have a color-only signal problem.
- Simulated color blindness. Browser extensions and Figma plugins simulate protanopia, deuteranopia, and tritanopia. Check all three. What looks like a clear color distinction to you may be invisible to someone with deuteranopia.
- Real users. If your product has a large enough user base, recruit participants with color vision differences to test critical flows.
Generate and test your full palette in one pass with Color Palette Generator, which includes a "simulate" view that shows the palette as it would appear to common color blindness types. Combine with a grayscale check in DevTools (CSS filter: grayscale(1)) for a quick second opinion.
Dark mode without starting over
Designing dark mode is not inverting the light mode. It is a parallel palette where the roles are the same but the actual colors are different. A few practical rules:
- Reduce saturation. Vivid colors that work on white backgrounds become visually loud on dark backgrounds. Drop chroma by 20-40% for most accents.
- Avoid pure black. Material Design recommends #121212 (dark gray) as the base instead of #000000. Pure black has too much contrast against white text and feels harsh.
- Elevate with lightness, not shadow. On dark backgrounds, raised surfaces should get slightly lighter, not cast stronger shadows. Material's elevation system is worth studying here.
- Re-check contrast. All your contrast checks need to be redone on the dark palette. White on dark gray and black on white have different physiological responses.
CSS custom properties make this mechanically straightforward. Define your palette as CSS variables in :root, then override them in a [data-theme="dark"] block or under @media (prefers-color-scheme: dark). Every component references the variables, never raw color values.
Adjacent tools worth bookmarking
Related design utilities that extend color work: Color Gradient Generator for testing multi-stop gradients against the same palette, Gradient Text Generator for headline color treatments, CSS Filter Generator for adjustment effects like saturate and hue-rotate, Mesh Gradient Generator for organic backgrounds built from the palette, and CSS Flexbox Generator when you need to lay out palette swatches for a style guide.
Related pillar guide
This cluster post is part of the design and image track. For the broader foundation on visual optimization for the web, see Image Optimization: The Complete Guide.
FAQ
Is OKLCH browser-supported yet?
Yes, across all modern browsers. You can use it in production today for color values, gradients, and color interpolation hints. Legacy fallbacks are rarely needed outside of very specific enterprise contexts.
How many colors should my palette have?
One primary, one or two accents, a neutral scale (9 stops), and four semantic colors. Around 20 variables total. Every extra color is a maintenance burden and a decision users have to parse.
What contrast ratio do I need for WCAG AAA?
7:1 for normal text, 4.5:1 for large text. AAA is a stretch goal; AA (4.5:1 for normal) is the practical bar most products aim for.
Should I use color to indicate errors?
Use color as one signal among several. Always pair red error states with an icon and text — never assume the color is readable to everyone. Accessibility and visual clarity improve together here; it is not a trade-off.
How do I pick a primary color?
Start from brand constraints (what the logo already is), then check that the chosen color can produce a full lightness scale that passes contrast at both ends. Some brand colors look great at the midpoint and fail at the extremes — test before committing.
Closing thought
Color theory on the web is the intersection of aesthetics and accessibility, and the two are not in tension. Palettes that pass contrast checks and survive color blindness simulation are almost always more aesthetically confident than the ones that look pretty on one designer's screen. Start from a perceptually uniform color space, check every pair, and the rest follows.