Color Theory for Web Designers: Building Accessible Palettes
How to build harmonious, accessible color palettes that work in light and dark mode — without leaning on intuition you don't have yet.
By Syed Husnain Haider Bukhari · · Updated
Color is the most emotional element of a design and the most underestimated technical decision. A good palette feels effortless. A bad palette makes every page look amateur, even when typography and layout are immaculate. Most developers learn color by copying palettes from established sites — which works, until it's your turn to build a brand from scratch.
This article walks through the color theory you actually need to design for the web in 2026. We'll cover hue, saturation, lightness; color harmonies; contrast and accessibility; semantic palettes; and the specifics of designing for both light and dark modes simultaneously.
HSL, OKLCH, and why hex codes lie
Designers historically used hex codes (`#3b82f6`) because they fit in CSS. Hex codes are convenient but unintuitive — there's no obvious way to say "this color, but 20% darker" by editing the digits. HSL (hue, saturation, lightness) is more intuitive but has a known flaw: equal HSL lightness values across different hues do not look equally bright. A `hsl(60, 100%, 50%)` yellow appears far brighter than a `hsl(240, 100%, 50%)` blue at the same nominal lightness.
OKLCH solves this. It's a perceptually uniform color space — equal lightness values look equally bright across the entire spectrum. Modern CSS supports OKLCH natively (`oklch(70% 0.15 240)`), and design tokens defined in OKLCH produce more harmonious palettes with less manual tweaking. If you're picking a system for new work in 2026, use OKLCH.
Color harmonies: the easy way to look intentional
Color theory offers a few classic relationships that almost always look good. Monochromatic uses one hue at multiple lightness values — calm, cohesive, sometimes a bit muted. Analogous uses three adjacent hues on the color wheel — warm and harmonious. Complementary uses two opposite hues — high contrast, used sparingly. Triadic uses three hues at 120 degrees from each other — vibrant but balanced.
A practical starting point: pick one primary hue (the brand color), one neutral (a near-grey with a slight tint of the primary), and one accent (often the complement, used at very small areas for emphasis). That's enough for 90% of UI work. Add semantic colors (success green, warning amber, error red, info blue) once you need them.
Contrast and WCAG
WCAG 2.2 specifies minimum contrast ratios between text and its background: 4.5:1 for normal-size text, 3:1 for large text (18pt+ or 14pt+ bold). Failing these ratios excludes users with low vision and looks washed out even to users with perfect sight.
Tools like the WebAIM contrast checker, Chrome's DevTools color picker, and Figma's contrast plugin compute ratios for you. A common pitfall is light gray placeholder text on white — visually subtle but often only 2:1, well below the threshold. Either darken the text or accept that placeholder text is essentially decorative.
The newer APCA (Accessible Perceptual Contrast Algorithm) is being considered for WCAG 3. It correlates better with perception, especially at extreme lightness values. Worth knowing about but WCAG 2.x ratios remain the requirement in 2026.
Semantic color tokens
Don't reference raw colors directly in component code. Instead define semantic tokens — `--color-bg`, `--color-text`, `--color-primary`, `--color-border`, `--color-success` — and use them everywhere. When you redesign or add dark mode, you change a handful of token values instead of hunting through every component.
A reasonable starter set: background, surface, surface-2 (cards on top of surface), text, text-muted, border, primary, primary-foreground, accent, accent-foreground, success, warning, error, info, plus a `-foreground` variant for each color that needs text on it. That's about 15 tokens — enough to express any UI, simple enough to maintain.
Designing for dark mode from day one
Dark mode isn't "the same colors but inverted." Saturated colors that look beautiful on a light background often glow uncomfortably on a dark background. Pure white text on pure black background produces eye strain. Real dark-mode design uses slightly desaturated brand colors, off-white text (around `#e6e6e6`), and dark gray backgrounds (around `#0d0d0d` to `#1a1a1a`) instead of pure black.
The cleanest workflow is to define your design tokens twice — once for light mode, once for dark — and switch via CSS media queries (`@media (prefers-color-scheme: dark)`) or a `data-theme` attribute on the root element. Modern frameworks (Tailwind, Mantine, shadcn/ui) all support this pattern out of the box.
Color blindness and visual diversity
About 8% of men and 0.5% of women have some form of color blindness. The most common type (deuteranopia) makes red and green hard to distinguish. If your design relies on red vs green as the only differentiator — a status indicator with no icon, for instance — those users can't read it.
Always combine color with another cue: an icon, a label, a position. Test designs through a color-blindness simulator (Chrome DevTools and Figma both include one). Most fixes are tiny but unlock a noticeable user base.
Practical sources for inspiration
- Coolors.co, Color Hunt, Adobe Color — curated palettes for inspiration.
- Refactoring UI by Adam Wathan and Steve Schoger — the best practical book on UI color.
- Material Design 3 and Apple's Human Interface Guidelines — battle-tested color systems.
- Real-world brand sites you admire — sample their colors with the eyedropper, then study what makes the combination work.
Wrapping up
Color is part intuition, part craft, and part math. You can shortcut the intuition by leaning on harmonies and proven palettes; the craft comes from practice; the math (contrast ratios, OKLCH conversions) you can outsource to tools.
If you want to generate a complete palette in seconds, our free color palette generator produces complementary, analogous, triadic, and monochromatic schemes from any base color, with built-in contrast checking and dark-mode previews. Drop a brand color in, get a full UI palette out.