Color Blindness & Theming
Color blindness support retrofitted into an existing codebase is expensive. The right approach is to build it into your token architecture from day one: define colors through semantic CSS custom properties rather than raw hex values, and swap the entire palette by toggling a single class on the root element. This article walks through the pattern, the three most common color blindness types, and a practical SCSS implementation.
Why “We’ll Add It Later” Fails
Section titled “Why “We’ll Add It Later” Fails”A typical game UI hardcodes colors like background: #e74c3c (red for danger) and background: #2ecc71 (green for safe) directly into component stylesheets. When a colorblind accessibility request comes in, every component needs to be found and updated individually. The longer the codebase runs with hardcoded values, the more painful that audit becomes.
The architectural solution costs almost nothing to put in place before you write a single component: name your colors by semantic role, not by their value, and define them as CSS custom properties at the root level. Swapping the palette for any audience becomes a single override block.
The Three Main Types
Section titled “The Three Main Types”Knowing the most common types of color blindness tells you which color relationships to avoid and which alternative pairings are safe.
| Type | Approximate prevalence | What is affected |
|---|---|---|
| Deuteranopia / Deuteranomaly | ~6% of males | Red and green appear similar |
| Protanopia / Protanomaly | ~2% of males | Red appears very dark; confused with black |
| Tritanopia | Rare | Blue and yellow confused; rare but worth testing |
Red/green is by far the most common pairing to avoid. Elements that use red to mean “bad/danger” and green to mean “good/safe” are immediately ambiguous for roughly 8% of male players.
Step 1: Define a Semantic Color Token System
Section titled “Step 1: Define a Semantic Color Token System”Stop referencing raw hex values in component stylesheets. Instead, define a set of purpose-named custom properties at the root that carry all color values for the default theme.
// Default (standard vision) palette:root { // Status colors - semantic names, not "red" or "green" --color-status-danger: #e74c3c; // health low, threat indicator --color-status-safe: #2ecc71; // health full, success state --color-status-warning: #f39c12; // ammo low, caution state --color-status-neutral: #3498db; // informational, no urgency
// UI surface colors --color-bg-panel: #1a1a2e; --color-bg-overlay: rgba(0, 0, 0, 0.75);
// Text colors --color-text-primary: #f0f0f0; --color-text-secondary: #9e9e9e; --color-text-on-danger: #ffffff;}Every component in the codebase references these tokens, never a hardcoded hex value.
.health-bar { &__fill { // Uses the token, not a raw hex value background-color: var(--color-status-safe); }
&--low &__fill { background-color: var(--color-status-danger); }}Step 2: Define a Colorblind-Safe Override Block
Section titled “Step 2: Define a Colorblind-Safe Override Block”Create a second override block that redefines only the tokens that change for the colorblind-safe palette. Non-color tokens (backgrounds, text, surfaces) are inherited from :root and do not need to be repeated.
// Colorblind-safe palette override (deuteranopia / protanopia safe)// Replaces red/green with blue/orange - distinguishable to all common types:root.theme-colorblind { --color-status-danger: #e67e22; // orange replaces red --color-status-safe: #2980b9; // blue replaces green --color-status-warning: #8e44ad; // purple replaces yellow-orange // --color-status-neutral stays the same - already safe}The entire UI updates when .theme-colorblind is added to :root, because every component already uses var(--color-status-*). Zero component files need to be touched.
Step 3: Apply the Theme Class
Section titled “Step 3: Apply the Theme Class”The theme class can be applied at any time via JavaScript. The most practical approach is to read a player preference from the game engine and apply it once on load.
// Called once when the UI initializes, before first renderfunction applyAccessibilitySettings(playerPrefs) { if (playerPrefs.colorBlindMode) { // Single class toggle swaps the entire color palette document.documentElement.classList.add('theme-colorblind'); } else { document.documentElement.classList.remove('theme-colorblind'); }}If the setting can be changed at runtime (from the in-game settings menu), the same function can be called on preference change. Because all colors are CSS custom properties, the update is instant with no layout recalculation.
What to Avoid
Section titled “What to Avoid”Communicating state through color alone
Section titled “Communicating state through color alone”Color should not be the only indicator of a state change. A health bar that goes from green to red communicates nothing to a deuteranope. Add a secondary signal: an icon, a text label, a shape change, or an animation.
<!-- Only color differentiates states - fails for colorblind users --><div class="health-bar health-bar--low"></div>
<!-- Better: color + icon + aria-label all carry the state --><div class="health-bar health-bar--low" aria-label="Health critical: 12 of 100"> <span class="health-bar__icon health-bar__icon--warning">⚠</span> <div class="health-bar__fill"></div></div>Hardcoded color values in component files
Section titled “Hardcoded color values in component files”Any file that writes color: #e74c3c directly is opting out of the theming system. Enforce the use of tokens with a SCSS lint rule or a code review checklist.
Quick Checklist
Section titled “Quick Checklist”Use this to verify your UI meets a baseline accessibility standard for color:
- All status colors are defined as semantic custom properties, not hardcoded values
- A
.theme-colorblindoverride block exists and is tested with an actual palette simulation tool - No UI state is communicated by color alone (icon, label, or shape provides a secondary signal)
- The theme class can be toggled at runtime and updates instantly with no layout recalculation
- All colorblind-safe palette pairs have been verified with a contrast ratio tool (target: 4.5:1 for body text, 3:1 for large text and UI components)
© 2026 Coherent Labs. All rights reserved.