Skip to content

Accessible Color Systems — Building Inclusive Color Palettes

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Accessible Color Systems. We cover key concepts, practical examples, and best practices to help you master this topic.

Accessible color systems use WCAG-validated contrast ratios, distinguish content through multiple cues beyond color, provide sufficient color for meaning, and include light and dark mode variants for inclusivity.

What You'll Learn

You will learn how to build a color system that meets WCAG contrast requirements, how to use color accessibly without relying on color alone, and how to support light and dark modes.

Why It Matters

Color is one of the most common Accessibility failures. Low contrast, color-only indicators, and inaccessible color combinations exclude users with visual impairments. An accessible color system prevents these failures.

Real-World Use

DodaKit's color system includes a 10-color palette with validated contrast. Each color has a dark variant. Status colors (success, warning, error) are paired with icons so meaning is not conveyed by color alone.

flowchart TD
  A[Color System] --> B[Primary Palette]
  A --> C[Neutral Palette]
  A --> D[Semantic Colors]
  A --> E[Dark Mode]
  A --> F[Contrast Validation]
  B --> G[Brand colors + variants]
  C --> H[Text, background, border]
  D --> I[Success, warning, error, info]
  E --> J[Inverted or adjusted]
  F --> K[All pairs >= 4.5:1]

Building the Palette

Primary Palette

Create 5 to 10 variants of each primary brand color, from lightest to darkest. The middle variants typically work for text and backgrounds.

Neutral Palette

Define grays for text, backgrounds, borders, and surfaces. Ensure text grays meet 4.5:1 on their intended backgrounds.

Semantic Colors

Create color tokens for status: success (green), warning (yellow or orange), error (red), and info (blue). Each must meet contrast on its background.

Not Using Color Alone

WCAG SC 1.4.1 Use of Color requires that color is not the only way to convey meaning. Pair color with text, icons, patterns, or underlines.

// Color system with accessibility validation
class AccessibleColorSystem {
  constructor() {
    this.palette = {};
    this.pairs = [];
  }

  addColor(name, hex, type) {
    const luminance = this.getLuminance(hex);
    this.palette[name] = {
      hex: hex,
      type: type,
      luminance: luminance,
      light: luminance > 0.5
    };
  }

  addPair(foreground, background, purpose) {
    const fg = this.palette[foreground];
    const bg = this.palette[background];
    if (!fg || !bg) return { error: 'Color not found' };

    const contrast = this.calculateContrast(fg.luminance, bg.luminance);

    const pair = {
      foreground: foreground,
      background: background,
      purpose: purpose,
      contrast: Math.round(contrast * 100) / 100,
      passesAA: contrast >= 4.5,
      passesAAA: contrast >= 7.0,
      status: contrast >= 4.5 ? 'AA Pass' : contrast >= 3.0 ? 'Large Only' : 'Fail'
    };

    this.pairs.push(pair);
    return pair;
  }

  getLuminance(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];
  }

  calculateContrast(l1, l2) {
    const lighter = Math.max(l1, l2);
    const darker = Math.min(l1, l2);
    return (lighter + 0.05) / (darker + 0.05);
  }

  getFailingPairs() {
    return this.pairs.filter(p => p.status === 'Fail');
  }
}

const colors = new AccessibleColorSystem();
colors.addColor('text-primary', '#1a1a1a', 'text');
colors.addColor('text-secondary', '#505050', 'text');
colors.addColor('background', '#ffffff', 'background');
colors.addColor('surface', '#f5f5f5', 'background');
colors.addColor('error', '#cc3300', 'semantic');

console.log(colors.addPair('text-primary', 'background', 'Body text'));
console.log(colors.addPair('error', 'background', 'Error text'));
console.log('Failing pairs:', colors.getFailingPairs().length);

Expected output:

{ foreground: 'text-primary', background: 'background', purpose: 'Body text', contrast: 15.35, passesAA: true, passesAAA: true, status: 'AA Pass' }
{ foreground: 'error', background: 'background', purpose: 'Error text', contrast: 5.67, passesAA: true, passesAAA: false, status: 'AA Pass' }
Failing pairs: 0

Dark Mode

Create dark mode color variants. Invert the luminance relationship: light text on dark backgrounds. Validate all pairs in dark mode separately.

<!-- Accessible color usage with icon support -->
<div class="status-message" role="status" aria-live="polite">
  <span class="status-icon" aria-hidden="true">&#10003;</span>
  <span class="status-text">Scan completed successfully. No threats found.</span>
</div>

<style>
  .status-message {
    display: flex;
    align-items: center;
    gap: var(--ds-space-sm);
    padding: var(--ds-space-md);
    background: var(--ds-color-surface);
    border-left: 4px solid var(--ds-color-success);
  }
  .status-icon {
    color: var(--ds-color-success);
    font-size: 1.25rem;
  }
  .status-text {
    color: var(--ds-color-text-primary);
  }
</style>

Common Mistakes

1. Color-Only Status Indicators

Using red text or green backgrounds alone to convey status violates SC 1.4.1. Always add an icon, text label, or pattern.

2. Low Contrast Gray Text

Gray text on white is a common pattern. Many grays fail 4.5:1. Use a darker gray or black.

3. Not Testing All Color Combinations

A primary color that works on white may fail on a colored background. Test every intended pair.

4. Ignoring Dark Mode Contrast

A color system that works in light mode may fail in dark mode. Validate both.

Blue text is the standard for links. If using another color, ensure the link is identifiable by more than color, such as underlining.

6. No Focus on Interactive Colors

Hover, active, and visited states must also meet contrast. Validate interactive color variants.

7. Semantic Colors That Do Not Meet Contrast

Error red, warning yellow, and success green are often low contrast. Choose semantic colors that pass WCAG.

Practice Questions

1. Which SC requires that color is not the only way to convey meaning?

SC 1.4.1 Use of Color (Level A). Information must be available through more than one sensory channel.

2. What is the minimum contrast ratio for body text on a background?

4.5:1 for normal-size text, 3:1 for large text (18px bold or 24px regular).

3. Why should semantic colors be paired with icons?

Users who cannot perceive color differences still understand the status through the icon.

4. How does dark mode change color accessibility requirements?

Backgrounds are dark and text is light. The luminance relationship inverts. All pairs must be re-validated.

5. Challenge: Create a semantic color system with 4 status colors. For each, provide a color swatch, an icon, and a text label. Validate all color pairs.

FAQ

Can I use colored backgrounds on buttons?

Yes. Ensure the text or icon on the button meets 4.5:1 on the button background color.

How many color variants do I need per brand color?

5 to 10 variants from lightest to darkest. 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 is a common scale.

What is the safest text color combination?

Nearly black (#1a1a1a) on white (#ffffff) gives approximately 15:1 contrast, exceeding all requirements.

How do I choose accessible semantic colors?

Select colors that are distinguishable by hue and lightness. Blue for info, green for success, amber for warning, red for error.

Should I provide a high contrast mode option?

Yes. WCAG SC 1.4.6 requires 7:1 contrast for AAA. Provide a high contrast theme for users who need it.

Mini Project

Create an accessible color system for a design system. Include primary palette (5 variants), neutral palette (5 variants), and 4 semantic colors. Validate every foreground-background pair. Provide dark mode variants.

What's Next

Learn about accessible Typography System for design systems. Then explore Spacing and Layout for accessibility.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro