Color blindness · Design workflow

Design for red–green color blindness so the meaning survives when red and green look alike

Red–green deficiency is the common kind: 7.4% of men and 0.5% of women of European ancestry in the studies Sharpe and colleagues collected, and deuteranomaly alone is 4.63% of men. Reds, oranges, browns and greens drift together, so a design that tells them apart by hue alone fails these readers. This is the workflow that fixes it, with four worked examples measured in simulated vision.

One red and one green, as each type sees them
Typical vision71.8 Distinct
Protanopia24.9 Distinct
Deuteranopia4.8 Close match
Protanomaly31.7 Distinct
Deuteranomaly20.2 Distinct

The number is the CIEDE2000 distance between the two.

Red–green, men of European ancestry
7.4%
Deuteranomaly alone, men
4.63%
Examples, each measured
4

The problem

What changes, and what survives

In protanopia and deuteranopia one kind of cone is missing; in protanomaly and deuteranomaly it is shifted. Either way, colors that differ only along the red–green direction come together, while lightness and the blue–yellow direction survive. A simulation shows that loss of difference, not what any one person sees, and it is no diagnosis.

What still works

  • A difference in lightness: dark against light reads the same to everyone.
  • Blue against orange or yellow: that direction is left intact.
  • Anything that is not color: a word, an icon or shape, a pattern, a position.

The workflow

Five steps, in order

The order matters: the first step makes the others forgiving, and the last one checks the model against people.

  1. 1

    Give the meaning a second carrier first

    Before choosing any color, decide how the meaning reads without it: a word, an icon or shape, a pattern, or a position. WCAG 1.4.1 asks for exactly this.

    WCAG 1.4.1 Use of Color
  2. 2

    Separate the colors by lightness

    Pick colors that differ in lightness, so they stay apart even in grayscale, and give every mark 3:1 against its background, as WCAG 1.4.11 asks of graphics.

    Measure contrast
  3. 3

    Move the hues off the red–green axis

    When hue must change, set blue against orange or vermilion, as the Okabe–Ito palette does, instead of red against green. For a diverging scale, use purple–orange or red–blue in place of red–yellow–green.

    Chart palettes
  4. 4

    Simulate, and measure the distance

    Look at the design as protanopia and deuteranopia see it, and measure the colors that must differ. On this site’s scale, a CIEDE2000 distance under 10 is only noticeable and under 5 is a close match: too little to carry meaning on its own.

    Simulate a page
  5. 5

    Ask people who have it

    A simulation is a model of one dimension of vision. Ask colleagues and users with color vision deficiency to use the real thing, and change what they stumble on.

    People in the testing loop

Worked examples

Four examples, before and after

Each example is drawn twice: as it is often built, and fixed. Switch the view to see both as a red–green type does. The numbers under each are measured, not judged.

A line chart

Two series told apart by a red and a green line and a legend in the corner: the most common chart there is, and under deuteranopia its two lines are a close match.

As often built

Sales Returns

020406080
Fixed
020406080

What changed

  • Blue and vermilion from the Okabe–Ito palette, both 3:1 or more on white.
  • A label at the end of each line, so nobody matches colors to a legend.
  • Circles on one line and squares on the other, and one line dashed: three carriers besides hue.

Measured

A line chart: CIEDE2000 between the colors that must differ, in each view
Colors that must differTypical visionProtanopiaDeuteranopiaProtanomalyDeuteranomalyGrayscale
As often built #D62728 #2CA02C71.8 Distinct24.9 Distinct4.8 Close match31.7 Distinct20.2 Distinct10.8 Distinct
Fixed #0072B2 #D55E0049.6 Distinct50.0 Distinct56.5 Distinct48.0 Distinct52.5 Distinct8.3 Noticeable
The fixed version’s markup
<div class="rg-chart">
<svg class="rg-svg" viewBox="0 0 340 196" role="img" aria-label="Sales and returns, January to June: sales rise from 42 to 68, returns fall from 31 to 20">
<line x1="44" y1="168" x2="262" y2="168" stroke="#E5E7EB"/>
<text x="36" y="172" text-anchor="end" class="rg-tick" translate="no">0</text>
<line x1="44" y1="131.5" x2="262" y2="131.5" stroke="#E5E7EB"/>
<text x="36" y="135.5" text-anchor="end" class="rg-tick" translate="no">20</text>
<line x1="44" y1="95" x2="262" y2="95" stroke="#E5E7EB"/>
<text x="36" y="99" text-anchor="end" class="rg-tick" translate="no">40</text>
<line x1="44" y1="58.5" x2="262" y2="58.5" stroke="#E5E7EB"/>
<text x="36" y="62.5" text-anchor="end" class="rg-tick" translate="no">60</text>
<line x1="44" y1="22" x2="262" y2="22" stroke="#E5E7EB"/>
<text x="36" y="26" text-anchor="end" class="rg-tick" translate="no">80</text>
<polyline points="44.0,91.3 87.6,80.4 131.2,67.6 174.8,71.3 218.4,56.7 262.0,43.9" fill="none" stroke="#0072B2" stroke-width="2.5" stroke-linejoin="round"/>
<polyline points="44.0,111.4 87.6,116.9 131.2,107.8 174.8,118.7 218.4,124.2 262.0,131.5" fill="none" stroke="#D55E00" stroke-width="2.5" stroke-linejoin="round" stroke-dasharray="7 5"/>
<circle cx="44.0" cy="91.3" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="87.6" cy="80.4" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="131.2" cy="67.6" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="174.8" cy="71.3" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="218.4" cy="56.7" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="262.0" cy="43.9" r="4.5" fill="#0072B2" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="40.0" y="107.4" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="83.6" y="112.9" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="127.2" y="103.8" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="170.8" y="114.7" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="214.4" y="120.2" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<rect x="258.0" y="127.5" width="8" height="8" fill="#D55E00" stroke="#FFFFFF" stroke-width="1.5"/>
<circle cx="276" cy="43.900000000000006" r="4.5" fill="#0072B2"/>
<rect x="272" y="127.5" width="8" height="8" fill="#D55E00"/>
</svg>
<div class="rg-chart__words" aria-hidden="true">
<span class="rg-chart__t rg-chart__t--c" style="left:12.94%;top:92.86%">Jan</span>
<span class="rg-chart__t rg-chart__t--c" style="left:25.76%;top:92.86%">Feb</span>
<span class="rg-chart__t rg-chart__t--c" style="left:38.59%;top:92.86%">Mar</span>
<span class="rg-chart__t rg-chart__t--c" style="left:51.41%;top:92.86%">Apr</span>
<span class="rg-chart__t rg-chart__t--c" style="left:64.24%;top:92.86%">May</span>
<span class="rg-chart__t rg-chart__t--c" style="left:77.06%;top:92.86%">Jun</span>
<span class="rg-chart__t rg-chart__t--l" style="left:84.12%;top:22.40%">Sales</span>
<span class="rg-chart__t rg-chart__t--l" style="left:84.12%;top:67.09%">Returns</span>
</div>
</div>

Status in a table

A green dot for up and a red one for down. Even the site’s own success and error colors, chosen for contrast, come close under simulation and nearly meet in grayscale.

As often built
ServerStatus
Mumbai
Pune
Delhi
Chennai

Online Offline

Fixed
ServerStatus
MumbaiOnline
PuneOffline
DelhiOnline
ChennaiOffline

What changed

  • A word in every cell: the state is read, not decoded.
  • A tick and a cross: two shapes nobody confuses.
  • The colors stay as a third cue, measured for contrast against their tints.

Measured

Status in a table: CIEDE2000 between the colors that must differ, in each view
Colors that must differTypical visionProtanopiaDeuteranopiaProtanomalyDeuteranomalyGrayscale
As often built #2E9E4F #D7263D72.4 Distinct24.3 Distinct5.7 Noticeable32.9 Distinct21.0 Distinct10.1 Distinct
Fixed #136B39 #B91C1C61.1 Distinct8.6 Noticeable12.8 Distinct23.1 Distinct19.9 Distinct0.4 Imperceptible
The fixed version’s markup
<table class="rg-table">
                                <thead>
<tr>
<th scope="col">Server</th>
<th scope="col">Status</th>
</tr>
</thead>
                                <tbody>
<tr>
<th scope="row">Mumbai</th>
<td>
<span class="rg-badge" style="color:#136B39;background:#E8F5EE">
<svg class="rg-ico" viewBox="0 0 16 16" aria-hidden="true">
<path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>Online</span>
</span>
</td>
</tr>
<tr>
<th scope="row">Pune</th>
<td>
<span class="rg-badge" style="color:#B91C1C;background:#FEE2E2">
<svg class="rg-ico" viewBox="0 0 16 16" aria-hidden="true">
<path d="M4 4l8 8M12 4l-8 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<span>Offline</span>
</span>
</td>
</tr>
<tr>
<th scope="row">Delhi</th>
<td>
<span class="rg-badge" style="color:#136B39;background:#E8F5EE">
<svg class="rg-ico" viewBox="0 0 16 16" aria-hidden="true">
<path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>Online</span>
</span>
</td>
</tr>
<tr>
<th scope="row">Chennai</th>
<td>
<span class="rg-badge" style="color:#B91C1C;background:#FEE2E2">
<svg class="rg-ico" viewBox="0 0 16 16" aria-hidden="true">
<path d="M4 4l8 8M12 4l-8 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<span>Offline</span>
</span>
</td>
</tr>
</tbody>
                            </table>

A form field

Validity shown by the border alone: green for good, red for wrong. To a red–green reader the two fields look almost the same, and a screen reader hears nothing at all.

As often built
Fixed

Error: enter a 10-digit phone number

What changed

  • An error message in words, starting with “Error”, tied to the field with aria-describedby.
  • An icon beside the message and a thicker border on the field in error.
  • Valid fields need no color at all: they simply have no message.

Measured

A form field: CIEDE2000 between the colors that must differ, in each view
Colors that must differTypical visionProtanopiaDeuteranopiaProtanomalyDeuteranomalyGrayscale
As often built #2E9E4F #D7263D72.4 Distinct24.3 Distinct5.7 Noticeable32.9 Distinct21.0 Distinct10.1 Distinct
Fixed #6B7280 #B91C1C33.8 Distinct30.5 Distinct32.3 Distinct30.3 Distinct31.0 Distinct7.6 Noticeable
The fixed version’s markup
<div class="rg-ff">
<label for="rg-after-email">Email</label>
<input id="rg-after-email" type="email" value="[email protected]" readonly style="border:1px solid #6B7280">
</div>
                            <div class="rg-ff">
<label for="rg-after-phone">Phone</label>
<input id="rg-after-phone" type="tel" value="98765 4321" readonly aria-invalid="true" aria-describedby="rg-after-phone-error" style="border:3px solid #B91C1C">
<p class="rg-error" id="rg-after-phone-error" style="color:#B91C1C">
<svg class="rg-ico" viewBox="0 0 16 16" aria-hidden="true">
<path d="M4 4l8 8M12 4l-8 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<span>Error: enter a 10-digit phone number</span>
</p>
</div>

A heatmap

Growth by region and quarter on a red–yellow–green scale, with no numbers. Under deuteranopia its two ends are only noticeably apart, so falls and rises blur.

As often built
Growth on last year by region and quarter, shown by color alone
Q1Q2Q3Q4
North
South
East
West
Fixed
Growth on last year by region and quarter, with each value printed; cells below zero are hatched
Q1Q2Q3Q4
North−6−2+3+8
South+4+7+9+12
East−9−4+1+5
West+2−1−3+6

Hatched: below zero

What changed

  • Purple–orange in place of red–yellow–green: its ends stay distinct in every simulation.
  • The value printed in every cell, in whichever ink reads best on it.
  • A hatch pattern on every cell below zero: a pattern carries the sign without any color.

Measured

A heatmap: CIEDE2000 between the colors that must differ, in each view
Colors that must differTypical visionProtanopiaDeuteranopiaProtanomalyDeuteranomalyGrayscale
As often built #D73027 #1A985068.3 Distinct19.0 Distinct9.6 Noticeable27.7 Distinct20.8 Distinct7.1 Noticeable
Fixed #B35806 #54278850.3 Distinct54.4 Distinct57.9 Distinct54.2 Distinct56.6 Distinct17.4 Distinct
The fixed version’s markup
<table class="rg-heat">
                                <caption class="aa2-sr">Growth on last year by region and quarter, with each value printed; cells below zero are hatched</caption>
                                <thead>
<tr>
<td>
</td>
<th scope="col" translate="no">Q1</th>
<th scope="col" translate="no">Q2</th>
<th scope="col" translate="no">Q3</th>
<th scope="col" translate="no">Q4</th>
</tr>
</thead>
                                <tbody>
<tr>
<th scope="row">North</th>
<td class="rg-heat__c is-below" style="--c:#FDB863">
<span class="rg-heat__v" style="color:#000000" translate="no">−6</span>
</td>
<td class="rg-heat__c is-below" style="--c:#FEE0B6">
<span class="rg-heat__v" style="color:#000000" translate="no">−2</span>
</td>
<td class="rg-heat__c" style="--c:#D8DAEB">
<span class="rg-heat__v" style="color:#000000" translate="no">+3</span>
</td>
<td class="rg-heat__c" style="--c:#8073AC">
<span class="rg-heat__v" style="color:#000000" translate="no">+8</span>
</td>
</tr>
<tr>
<th scope="row">South</th>
<td class="rg-heat__c" style="--c:#D8DAEB">
<span class="rg-heat__v" style="color:#000000" translate="no">+4</span>
</td>
<td class="rg-heat__c" style="--c:#B2ABD2">
<span class="rg-heat__v" style="color:#000000" translate="no">+7</span>
</td>
<td class="rg-heat__c" style="--c:#8073AC">
<span class="rg-heat__v" style="color:#000000" translate="no">+9</span>
</td>
<td class="rg-heat__c" style="--c:#542788">
<span class="rg-heat__v" style="color:#FFFFFF" translate="no">+12</span>
</td>
</tr>
<tr>
<th scope="row">East</th>
<td class="rg-heat__c is-below" style="--c:#E08214">
<span class="rg-heat__v" style="color:#000000" translate="no">−9</span>
</td>
<td class="rg-heat__c is-below" style="--c:#FEE0B6">
<span class="rg-heat__v" style="color:#000000" translate="no">−4</span>
</td>
<td class="rg-heat__c" style="--c:#F7F7F7">
<span class="rg-heat__v" style="color:#000000" translate="no">+1</span>
</td>
<td class="rg-heat__c" style="--c:#B2ABD2">
<span class="rg-heat__v" style="color:#000000" translate="no">+5</span>
</td>
</tr>
<tr>
<th scope="row">West</th>
<td class="rg-heat__c" style="--c:#D8DAEB">
<span class="rg-heat__v" style="color:#000000" translate="no">+2</span>
</td>
<td class="rg-heat__c is-below" style="--c:#F7F7F7">
<span class="rg-heat__v" style="color:#000000" translate="no">−1</span>
</td>
<td class="rg-heat__c is-below" style="--c:#FEE0B6">
<span class="rg-heat__v" style="color:#000000" translate="no">−3</span>
</td>
<td class="rg-heat__c" style="--c:#B2ABD2">
<span class="rg-heat__v" style="color:#000000" translate="no">+6</span>
</td>
</tr>
</tbody>
                            </table>
<p class="rg-key">
<span class="rg-key__sw" aria-hidden="true">
</span>
<span>Hatched: below zero</span>
</p>

Your own colors

Check yours

The same measurements, on your own colors and pages.

Where this comes from

Sources

Found a mistake? Write to us and we will correct it.