Skip to content

Web Color Theory — Color Palettes & Accessibility Guide

DodaTech Updated 2026-06-21 9 min read

In this tutorial, you'll learn about Web Color Theory. We cover key concepts, practical examples, and best practices.

Color theory is the practice of using colour effectively in design — understanding hue, saturation, contrast, and accessibility to create interfaces that are both beautiful and usable by everyone.

What You'll Learn

  • The colour wheel and harmonious palette types
  • How contrast ratios work and WCAG compliance
  • Building accessible colour systems with CSS

Why It Matters

Colour is the first thing users notice — it affects emotion, readability, and brand recognition. 8% of men and 0.5% of women have some form of colour blindness. If your UI relies only on colour to convey information, you exclude millions of users. Accessibility-friendly colour choices improve usability for everyone, especially in bright sunlight or on low-quality screens.

Real-world use: Doda Browser's UI uses a carefully chosen colour palette that works for colour-blind users. Durga Antivirus Pro uses red for critical threats, yellow for warnings, and green for safe — but also adds icons and text labels so colour is not the only differentiator.

Colour Fundamentals

flowchart LR
  A[Hue] --> B[Saturation]
  B --> C[Lightness]
  C --> D[Color Space:
sRGB, HSL, OKLCH] D --> E[Palette
Types] E --> F[Contrast &
Accessibility] style A fill:#f90,color:#fff style C fill:#4af,color:#fff style E fill:#4a4,color:#fff style F fill:#f4a,color:#fff

HSL Colour Model

HSL (Hue, Saturation, Lightness) is the most intuitive way to define colours in CSS:

  • Hue: The colour on a 0–360 degree wheel (0=red, 120=green, 240=blue)
  • Saturation: Intensity from 0% (grey) to 100% (full colour)
  • Lightness: Brightness from 0% (black) to 100% (white)

Palette Types

Type How It Works Best For
Monochromatic One hue with varying lightness/ saturation Clean, minimalist UIs, data visualisation
Analogous 3 adjacent hues on the colour wheel Calm, harmonious designs
Complementary 2 opposite hues on the colour wheel High contrast, call-to-action buttons
Triadic 3 evenly spaced hues (120° apart) Vibrant, playful interfaces
Tetradic 2 complementary pairs (4 colours total) Complex dashboards, data-rich apps

Building Accessible Colour Systems

Example 1: Creating a Colour Palette with CSS Custom Properties

/* Accessible color system using HSL */
:root {
  /* Primary palette */
  --color-primary-h: 222;
  --color-primary-s: 89%;
  --color-primary-50: hsl(var(--color-primary-h), var(--color-primary-s), 97%);
  --color-primary-100: hsl(var(--color-primary-h), var(--color-primary-s), 94%);
  --color-primary-200: hsl(var(--color-primary-h), var(--color-primary-s), 86%);
  --color-primary-300: hsl(var(--color-primary-h), var(--color-primary-s), 77%);
  --color-primary-400: hsl(var(--color-primary-h), var(--color-primary-s), 66%);
  --color-primary-500: hsl(var(--color-primary-h), var(--color-primary-s), 50%);
  --color-primary-600: hsl(var(--color-primary-h), var(--color-primary-s), 40%);
  --color-primary-700: hsl(var(--color-primary-h), var(--color-primary-s), 32%);
  --color-primary-800: hsl(var(--color-primary-h), var(--color-primary-s), 24%);
  --color-primary-900: hsl(var(--color-primary-h), var(--color-primary-s), 15%);

  /* Neutral palette */
  --color-neutral-50: #f9fafb;
  --color-neutral-100: #f3f4f6;
  --color-neutral-200: #e5e7eb;
  --color-neutral-300: #d1d5db;
  --color-neutral-400: #9ca3af;
  --color-neutral-500: #6b7280;
  --color-neutral-600: #4b5563;
  --color-neutral-700: #374151;
  --color-neutral-800: #1f2937;
  --color-neutral-900: #111827;

  /* Semantic colours */
  --color-success: #16a34a;
  --color-warning: #d97706;
  --color-error: #dc2626;
  --color-info: #2563eb;

  /* Surface & text */
  --color-bg: var(--color-neutral-50);
  --color-text: var(--color-neutral-900);
  --color-text-secondary: var(--color-neutral-500);
  --color-border: var(--color-neutral-200);
}

/* Apply the system */
body {
  background: var(--color-bg);
  color: var(--color-text);
}

.secondary-text {
  color: var(--color-text-secondary);
}

.btn-primary {
  background: var(--color-primary-500);
  color: white;
}

.btn-primary:hover {
  background: var(--color-primary-600);
}

Expected output: A consistent colour system applied across the UI. Buttons use var(--color-primary-500) for default state and darken to 600 on hover. Text is near-black (#111827) on near-white (#f9fafb) for maximum readability. Secondary text uses #6b7280 (gray-500) which has a 4.6:1 contrast ratio on white — meeting WCAG AA.

Example 2: Checking Contrast Ratio Programmatically

// Check WCAG contrast ratios in the browser
function calculateContrast(hex1, hex2) {
  function luminance(hex) {
    const rgb = hex.match(/[A-Fa-f0-9]{2}/g).map(c => {
      const v = parseInt(c, 16) / 255;
      return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
  }

  const L1 = luminance(hex1);
  const L2 = luminance(hex2);
  const ratio = (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
  return ratio;
}

// Test colour combinations
const tests = [
  { fg: '#111827', bg: '#f9fafb', label: 'Dark text on light bg' },
  { fg: '#6b7280', bg: '#ffffff', label: 'Gray-500 on white' },
  { fg: '#9ca3af', bg: '#ffffff', label: 'Gray-400 on white' },
  { fg: '#ffffff', bg: '#2563eb', label: 'White on blue-600' },
  { fg: '#dc2626', bg: '#ffffff', label: 'Red-600 on white' },
];

tests.forEach(({ fg, bg, label }) => {
  const ratio = calculateContrast(fg, bg);
  const aa = ratio >= 4.5 ? '✅ AA' : '❌ Fail';
  const aaa = ratio >= 7 ? '(AAA ✅)' : '(AAA ❌)';
  console.log(`${label}: ${ratio.toFixed(2)}:1 — ${aa} ${aaa}`);
});

Expected output:

Dark text on light bg: 15.05:1 — ✅ AA (AAA ✅)
Gray-500 on white: 4.62:1 — ✅ AA (AAA ❌)
Gray-400 on white: 3.23:1 — ❌ Fail (AAA ❌)
White on blue-600: 8.59:1 — ✅ AA (AAA ✅)
Red-600 on white: 4.08:1 — ❌ Fail (AAA ❌)

Note that Gray-400 (#9ca3af) and Red-600 (#dc2626) both fail WCAG AA for normal text. You'd need darker shades or larger text sizes to use these colours.

Example 3: OKLCH for Perceptually Uniform Colour

/* Modern browsers support OKLCH — perceptually uniform color space */
:root {
  /* OKLCH ensures consistent perceived lightness across hues */
  --blue: oklch(55% 0.2 250);
  --green: oklch(55% 0.2 145);
  --red: oklch(55% 0.2 30);
  --yellow: oklch(55% 0.2 90);
  --purple: oklch(55% 0.2 290);
  
  /* Why OKLCH matters:
     In HSL, yellow at 55% lightness looks much brighter than blue at 55%.
     In OKLCH, 55% lightness looks equally bright across all hues.
  */
}

.demo-colors {
  display: flex;
  gap: 1rem;
}

.swatch {
  width: 100px;
  height: 100px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}

.swatch-blue { background: var(--blue); }
.swatch-green { background: var(--green); }
.swatch-red { background: var(--red); }
.swatch-yellow { background: var(--yellow); }
.swatch-purple { background: var(--purple); }

Expected output: All five swatches appear equally bright despite being different hues. In HSL, the yellow would appear significantly brighter and the blue much darker. OKLCH solves this perceptual non-uniformity, making it easier to build balanced colour palettes.

WCAG Colour Requirements

Level Normal Text Large Text (18px+ bold or 24px+) UI Components
AA 4.5:1 3:1 3:1
AAA 7:1 4.5:1 Not required

Note: Colour should never be the only way to convey information. Always include icons, text labels, or patterns alongside colour.

Common Colour Errors

  1. Red-green colour reliance — Using red/green as the only differentiator (e.g., "items in red are blocked, green are safe"). 8% of men can't distinguish them. Add icons like a lock for blocked and shield for safe.

  2. Low contrast text — Light grey text (#999, #aaa) is popular but fails WCAG on white backgrounds. Always check contrast ratio. Use tools like the WebAIM Contrast Checker during design.

  3. Inconsistent saturation across a palette — A primary blue at 100% saturation next to a muted grey at 20% saturation creates visual imbalance. Keep saturation consistent within each tier of your palette.

  4. Not providing dark mode colours — Users in dark environments or with visual sensitivities prefer dark themes. Use prefers-color-scheme: dark media query and ensure all colour pairs meet contrast requirements in both modes.

  5. Using colour alone for link identification — Underlined links or different font weight help users distinguish links from regular text without relying on colour. WCAG requires links to be identifiable by more than colour.

  6. Overly vibrant background colours — Bright saturated backgrounds cause eye strain and make text unreadable. Use low-saturation, high-lightness variants for backgrounds (like hsl(222, 20%, 97%) instead of pure blue).

Frequently Asked Questions

What is the difference between AA and AAA contrast ratios?

WCAG AA requires a minimum of 4.5:1 for normal text and 3:1 for large text. AAA is a stricter standard requiring 7:1 for normal text and 4.5:1 for large text. AA is the legal minimum in most accessibility regulations. AAA is recommended for content aimed at older audiences or low-vision users.

Can I use brand colours and still meet accessibility requirements?

Yes, but you may need to adjust your brand palette. Use lighter tints for backgrounds (lightness 90%+), reserve the full-saturation version for small accent elements or large headings (where 3:1 contrast is sufficient), and ensure your primary brand colour is dark enough on white or light enough on dark to pass 4.5:1 for body text.

What tools can I use to check colour accessibility?

Several free tools help: WebAIM Contrast Checker (quick pair checks), Chrome DevTools (inspect any element and view the contrast ratio in the colour picker), Stark (Figma plugin for design phase), and axe DevTools (automated accessibility audits that flag contrast failures).

Practice Questions

  1. What is the difference between HSL and OKLCH colour spaces? HSL uses a cylindrical model that doesn't account for human perception — 50% lightness yellow looks brighter than 50% lightness blue. OKLCH is perceptually uniform: equal lightness values look equally bright across all hues.

  2. What contrast ratio does WCAG AA require for normal text? 4.5:1 minimum. Large text (18px+ bold or 24px+) needs 3:1. AAA requires 7:1 for normal text and 4.5:1 for large text.

  3. Why should you not use colour alone to convey information? Colour-blind users can't distinguish certain colour pairs. Additionally, screen readers don't convey colour. Always use icons, text labels, or patterns alongside colour.

  4. What is a monochromatic colour palette? A palette built from a single hue at varying lightness and saturation levels. It creates a cohesive, minimal look and is easy to make accessible because contrast is predictable.

  5. How do you implement dark mode with CSS custom properties? Define colour variables in :root for light mode, then override them inside @media (prefers-color-scheme: dark) { :root { ... } }. Use semantic variable names like --color-bg instead of --color-white.

Challenge

Build a complete accessible colour system for DodaTech: create 10-shade palettes for primary, success, warning, and error colours using HSL. Verify every shade pair against WCAG AA and AAA for text and large text. Add OKLCH equivalents for modern browsers. Create utility classes for text, background, border, and hover states. Test with a colour-blindness simulator (like Chrome DevTools rendering emulation).

Real-World Task

Durga Antivirus Pro's threat dashboard uses colour-coded severity badges: red for critical, orange for high, yellow for medium, green for low. A user base audit reveals 12% of enterprise customers use accessibility tools. Redesign the badges to include icons (skull, exclamation, warning, shield) and ensure every badge passes WCAG AA for both the badge text and the background colour contrast. Add a high-contrast mode toggle that switches to a text-only representation.


Related: Web Accessibility Guide | Related: CSS Preprocessors | Related: Responsive Design Guide

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro