OKLCH Studio · Accessible palettes · Data visualisation
Accessible chart colors: an 8-colour categorical palette
Updated 2026-10-19
Most chart colour advice stops at taste. This page gives you eight categorical colours that were selected by a program with two hard rules: every series colour must be at least 3:1 against the chart background, as WCAG 1.4.11 asks for graphical objects, and every pair of colours must stay apart when the palette is simulated for protanopia, deuteranopia and tritanopia.
Categorical charts, where each colour names a different thing, are the hardest case. Eight series is near the practical limit: beyond that, people cannot hold the legend in mind and no palette can fix that. If you have more than eight categories, group the tail as Other or use small multiples instead of more colours.
The palette comes in a light and a dark chart theme with matching text, axis and gridline roles. Chart text is held at AA, axis lines at 3:1, and gridlines are deliberately fainter because they are decoration. Pair colour with direct labels or markers, since WCAG 1.4.1 says colour should not be the only way to convey information.
How to build a palette like this by hand is explained in the accessible colour palette guide; this page is the finished, verified result.
The palette: light and dark
Hex values are the sRGB result after gamut mapping. The oklch() values are those of the final hex, so converting either gives the same colour.
Light chart: series, with contrast on the chart background
- 1. #004B988.29:1
- 2. #B484003.26:1
- 3. #7730009.24:1
- 4. #4790D83.26:1
- 5. #7D66225.37:1
- 6. #13748D5.21:1
- 7. #5D36669.33:1
- 8. #A543585.75:1
Light theme
- Chart background
#F9FCFF
oklch(0.990 0.005 247.9)
Sample text
- Chart text
#1D2022
oklch(0.241 0.006 236.8)
Sample text
- Axis labels
#55585C
oklch(0.459 0.008 255.5)
Sample text
- Axis line
#83878B
oklch(0.621 0.008 247.9)
Sample text
- Gridline
#CCD0D5
oklch(0.856 0.008 253.9)
Sample text
- Series 1
#004B98
oklch(0.421 0.142 255.6)
Sample text
- Series 2
#B48400
oklch(0.6433 0.1323 82.79)
Sample text
- Series 3
#773000
oklch(0.403 0.112 46.6)
Sample text
- Series 4
#4790D8
oklch(0.639 0.131 250.3)
Sample text
- Series 5
#7D6622
oklch(0.520 0.090 89.9)
Sample text
- Series 6
#13748D
oklch(0.5190 0.0903 220.91)
Sample text
- Series 7
#5D3666
oklch(0.401 0.091 319.9)
Sample text
- Series 8
#A54358
oklch(0.519 0.130 9.6)
Sample text
Dark chart: series, with contrast on the chart background
- 1. #86B8FF8.90:1
- 2. #CC59004.31:1
- 3. #FFBB6510.83:1
- 4. #5A76E44.44:1
- 5. #AC697E4.38:1
- 6. #CB8EB66.98:1
- 7. #86A0006.08:1
- 8. #63E6BE11.74:1
Dark theme
- Chart background
#141618
oklch(0.199 0.005 248.1)
Sample text
- Chart text
#E8EBEF
oklch(0.939 0.006 255.5)
Sample text
- Axis labels
#AEB1B6
oklch(0.760 0.008 260.7)
Sample text
- Axis line
#717579
oklch(0.560 0.008 248.0)
Sample text
- Gridline
#33373A
oklch(0.334 0.008 240.1)
Sample text
- Series 1
#86B8FF
oklch(0.775 0.115 257.2)
Sample text
- Series 2
#CC5900
oklch(0.600 0.166 47.2)
Sample text
- Series 3
#FFBB65
oklch(0.839 0.129 70.9)
Sample text
- Series 4
#5A76E4
oklch(0.600 0.169 269.9)
Sample text
- Series 5
#AC697E
oklch(0.600 0.089 359.9)
Sample text
- Series 6
#CB8EB6
oklch(0.719 0.090 340.2)
Sample text
- Series 7
#86A000
oklch(0.6620 0.1581 120.57)
Sample text
- Series 8
#63E6BE
oklch(0.841 0.130 170.1)
Sample text
Contrast table: every verified pair
Pass or fail uses the raw WCAG 2.x ratio; the ratio shown is floored to two decimals so a near miss is never rounded up. APCA Lc is signed: positive is dark on light, negative is light on dark. Each row opens the pair in the checker.
| Pair | WCAG ratio | Target | Normal AA / AAA | Large AA / AAA | APCA Lc | APCA tier | Check |
|---|---|---|---|---|---|---|---|
| Chart text on background#1d2022 on #f9fcff | 15.91:1 | AA 4.5:1 | Pass / Pass | Pass / Pass | +101.3 | Lc 75: body text | Open in checker |
| Axis labels on background#55585c on #f9fcff | 6.94:1 | AA 4.5:1 | Pass / Fail | Pass / Pass | +82.8 | Lc 75: body text | Open in checker |
| Axis line on background#83878b on #f9fcff | 3.51:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | +61.8 | Lc 60: larger content text | Open in checker |
| Series 1 on background#004b98 on #f9fcff | 8.29:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | +86.6 | Lc 75: body text | Open in checker |
| Series 2 on background#b48400 on #f9fcff | 3.26:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | +58.8 | Lc 45: headlines and large text | Open in checker |
| Series 3 on background#773000 on #f9fcff | 9.24:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | +89.3 | Lc 75: body text | Open in checker |
| Series 4 on background#4790d8 on #f9fcff | 3.26:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | +58.7 | Lc 45: headlines and large text | Open in checker |
| Series 5 on background#7d6622 on #f9fcff | 5.37:1 | 3:1 (WCAG 1.4.11) | Pass / Fail | Pass / Pass | +75.5 | Lc 75: body text | Open in checker |
| Series 6 on background#13748d on #f9fcff | 5.21:1 | 3:1 (WCAG 1.4.11) | Pass / Fail | Pass / Pass | +74.3 | Lc 60: larger content text | Open in checker |
| Series 7 on background#5d3666 on #f9fcff | 9.33:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | +90.0 | Lc 75: body text | Open in checker |
| Series 8 on background#a54358 on #f9fcff | 5.75:1 | 3:1 (WCAG 1.4.11) | Pass / Fail | Pass / Pass | +77.0 | Lc 75: body text | Open in checker |
| Pair | WCAG ratio | Target | Normal AA / AAA | Large AA / AAA | APCA Lc | APCA tier | Check |
|---|---|---|---|---|---|---|---|
| Chart text on background#e8ebef on #141618 | 15.16:1 | AA 4.5:1 | Pass / Pass | Pass / Pass | −93.7 | Lc 75: body text | Open in checker |
| Axis labels on background#aeb1b6 on #141618 | 8.43:1 | AA 4.5:1 | Pass / Pass | Pass / Pass | −59.1 | Lc 45: headlines and large text | Open in checker |
| Axis line on background#717579 on #141618 | 3.90:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | −28.5 | Below Lc 30 | Open in checker |
| Series 1 on background#86b8ff on #141618 | 8.90:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | −61.9 | Lc 60: larger content text | Open in checker |
| Series 2 on background#cc5900 on #141618 | 4.31:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | −32.6 | Lc 30: non-text and incidental | Open in checker |
| Series 3 on background#ffbb65 on #141618 | 10.83:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | −72.6 | Lc 60: larger content text | Open in checker |
| Series 4 on background#5a76e4 on #141618 | 4.44:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | −33.1 | Lc 30: non-text and incidental | Open in checker |
| Series 5 on background#ac697e on #141618 | 4.38:1 | 3:1 (WCAG 1.4.11) | Fail / Fail | Pass / Fail | −32.5 | Lc 30: non-text and incidental | Open in checker |
| Series 6 on background#cb8eb6 on #141618 | 6.98:1 | 3:1 (WCAG 1.4.11) | Pass / Fail | Pass / Pass | −50.4 | Lc 45: headlines and large text | Open in checker |
| Series 7 on background#86a000 on #141618 | 6.08:1 | 3:1 (WCAG 1.4.11) | Pass / Fail | Pass / Pass | −44.7 | Lc 30: non-text and incidental | Open in checker |
| Series 8 on background#63e6be on #141618 | 11.74:1 | 3:1 (WCAG 1.4.11) | Pass / Pass | Pass / Pass | −77.4 | Lc 75: body text | Open in checker |
Colour-blind check
Colours are simulated with the Machado et al. (2009) model at full severity (culori). The figure is the smallest ΔE-OK ×100, the Euclidean distance in OKLab, between any two colours. The build fails if it drops below 8. Still pair colour with labels or patterns (WCAG 1.4.1).
| Vision | Simulated colours | Min ΔE-OK ×100 | Closest pair |
|---|---|---|---|
| Normal vision | 12.9 (≥ 8) | 1 and 6 | |
| Protanopia | 8.0 (≥ 8) | 7 and 8 | |
| Deuteranopia | 8.7 (≥ 8) | 5 and 8 | |
| Tritanopia | 9.2 (≥ 8) | 5 and 8 |
| Vision | Simulated colours | Min ΔE-OK ×100 | Closest pair |
|---|---|---|---|
| Normal vision | 12.3 (≥ 8) | 5 and 6 | |
| Protanopia | 10.9 (≥ 8) | 3 and 8 | |
| Deuteranopia | 9.8 (≥ 8) | 6 and 8 | |
| Tritanopia | 9.6 (≥ 8) | 1 and 8 |
Why these colours
The generator builds a pool of candidates across the hue wheel at several lightness and chroma levels, drops any that fail 3:1 against the background, and then picks the set whose closest pair is as far apart as possible. Distance is measured in OKLab, scaled by 100, and the minimum is taken over normal vision and the three simulated deficiencies. The result is accepted only if that minimum is at least 8.
Because dichromats lose hue information, the selection leans on lightness differences as well as hue. You will see dark and light blues, a dark brown and an orange, in the same set. That variety is why the palette survives simulation, and why it looks less tidy than a rainbow. The light and dark chart themes are selected separately, since the lightness range allowed by 3:1 differs on each background.
The generator solved 22 pairs (11 light, 11 dark), and all of them meet their target on the raw WCAG ratio. The tightest is series 4 on background in the light theme at 3.26:1 against a target of 3:1.
How to use it
- Use the colours in the order given. The order was arranged so the first few are the most distinct, but the whole set was optimised, so using all eight gives the stated minimum difference.
- Label series directly on the chart where you can; otherwise add markers or dash patterns to lines.
- Do not use these colours for good and bad meanings, since they have no built-in order. Use a sequential or diverging scale for that.
- Outline pie slices and stacked bars with a thin line in the background colour, so adjacent slices separate even if two series look alike.
- For text inside a bar, check the label against that series colour; most series colours are mid-tones where neither black nor white is guaranteed.
Chart advice
- Limit lines to what a reader can follow, and highlight one series in a strong colour while greying the rest.
- Keep the legend close to the data, ordered the same way as the series appear.
- Provide the underlying numbers in a table or on hover and focus, for people who cannot perceive the chart.
Export
Generated from the verified values above, never typed by hand. Light and dark are both included.
CSS custom properties (hex, then oklch())
/* sRGB hex first: every browser. */
:root {
--color-background: #f9fcff;
--color-text: #1d2022;
--color-text-muted: #55585c;
--color-axis: #83878b;
--color-gridline: #ccd0d5;
--color-series-1: #004b98;
--color-series-2: #b48400;
--color-series-3: #773000;
--color-series-4: #4790d8;
--color-series-5: #7d6622;
--color-series-6: #13748d;
--color-series-7: #5d3666;
--color-series-8: #a54358;
}
.dark {
--color-background: #141618;
--color-text: #e8ebef;
--color-text-muted: #aeb1b6;
--color-axis: #717579;
--color-gridline: #33373a;
--color-series-1: #86b8ff;
--color-series-2: #cc5900;
--color-series-3: #ffbb65;
--color-series-4: #5a76e4;
--color-series-5: #ac697e;
--color-series-6: #cb8eb6;
--color-series-7: #86a000;
--color-series-8: #63e6be;
}
/* oklch() where supported: the same colours, unclipped values of the hex above. */
@supports (color: oklch(0 0 0)) {
:root {
--color-background: oklch(0.990 0.005 247.9);
--color-text: oklch(0.241 0.006 236.8);
--color-text-muted: oklch(0.459 0.008 255.5);
--color-axis: oklch(0.621 0.008 247.9);
--color-gridline: oklch(0.856 0.008 253.9);
--color-series-1: oklch(0.421 0.142 255.6);
--color-series-2: oklch(0.6433 0.1323 82.79);
--color-series-3: oklch(0.403 0.112 46.6);
--color-series-4: oklch(0.639 0.131 250.3);
--color-series-5: oklch(0.520 0.090 89.9);
--color-series-6: oklch(0.5190 0.0903 220.91);
--color-series-7: oklch(0.401 0.091 319.9);
--color-series-8: oklch(0.519 0.130 9.6);
}
.dark {
--color-background: oklch(0.199 0.005 248.1);
--color-text: oklch(0.939 0.006 255.5);
--color-text-muted: oklch(0.760 0.008 260.7);
--color-axis: oklch(0.560 0.008 248.0);
--color-gridline: oklch(0.334 0.008 240.1);
--color-series-1: oklch(0.775 0.115 257.2);
--color-series-2: oklch(0.600 0.166 47.2);
--color-series-3: oklch(0.839 0.129 70.9);
--color-series-4: oklch(0.600 0.169 269.9);
--color-series-5: oklch(0.600 0.089 359.9);
--color-series-6: oklch(0.719 0.090 340.2);
--color-series-7: oklch(0.6620 0.1581 120.57);
--color-series-8: oklch(0.841 0.130 170.1);
}
}Tailwind CSS v4 @theme
@theme {
/* light */
--color-background: oklch(0.990 0.005 247.9); /* #f9fcff */
--color-text: oklch(0.241 0.006 236.8); /* #1d2022 */
--color-text-muted: oklch(0.459 0.008 255.5); /* #55585c */
--color-axis: oklch(0.621 0.008 247.9); /* #83878b */
--color-gridline: oklch(0.856 0.008 253.9); /* #ccd0d5 */
--color-series-1: oklch(0.421 0.142 255.6); /* #004b98 */
--color-series-2: oklch(0.6433 0.1323 82.79); /* #b48400 */
--color-series-3: oklch(0.403 0.112 46.6); /* #773000 */
--color-series-4: oklch(0.639 0.131 250.3); /* #4790d8 */
--color-series-5: oklch(0.520 0.090 89.9); /* #7d6622 */
--color-series-6: oklch(0.5190 0.0903 220.91); /* #13748d */
--color-series-7: oklch(0.401 0.091 319.9); /* #5d3666 */
--color-series-8: oklch(0.519 0.130 9.6); /* #a54358 */
/* dark: use as dark:bg-background-dark, or remap under a .dark selector */
--color-background-dark: oklch(0.199 0.005 248.1); /* #141618 */
--color-text-dark: oklch(0.939 0.006 255.5); /* #e8ebef */
--color-text-muted-dark: oklch(0.760 0.008 260.7); /* #aeb1b6 */
--color-axis-dark: oklch(0.560 0.008 248.0); /* #717579 */
--color-gridline-dark: oklch(0.334 0.008 240.1); /* #33373a */
--color-series-1-dark: oklch(0.775 0.115 257.2); /* #86b8ff */
--color-series-2-dark: oklch(0.600 0.166 47.2); /* #cc5900 */
--color-series-3-dark: oklch(0.839 0.129 70.9); /* #ffbb65 */
--color-series-4-dark: oklch(0.600 0.169 269.9); /* #5a76e4 */
--color-series-5-dark: oklch(0.600 0.089 359.9); /* #ac697e */
--color-series-6-dark: oklch(0.719 0.090 340.2); /* #cb8eb6 */
--color-series-7-dark: oklch(0.6620 0.1581 120.57); /* #86a000 */
--color-series-8-dark: oklch(0.841 0.130 170.1); /* #63e6be */
}shadcn/ui variables (:root and .dark)
:root {
--background: oklch(0.990 0.005 247.9);
--foreground: oklch(0.241 0.006 236.8);
--muted-foreground: oklch(0.459 0.008 255.5);
--border: oklch(0.856 0.008 253.9);
--input: oklch(0.621 0.008 247.9);
--chart-1: oklch(0.421 0.142 255.6);
--chart-2: oklch(0.403 0.112 46.6);
--chart-3: oklch(0.520 0.090 89.9);
--chart-4: oklch(0.5190 0.0903 220.91);
--chart-5: oklch(0.519 0.130 9.6);
}
.dark {
--background: oklch(0.199 0.005 248.1);
--foreground: oklch(0.939 0.006 255.5);
--muted-foreground: oklch(0.760 0.008 260.7);
--border: oklch(0.334 0.008 240.1);
--input: oklch(0.560 0.008 248.0);
--chart-1: oklch(0.775 0.115 257.2);
--chart-2: oklch(0.839 0.129 70.9);
--chart-3: oklch(0.600 0.089 359.9);
--chart-4: oklch(0.719 0.090 340.2);
--chart-5: oklch(0.841 0.130 170.1);
}Design tokens (DTCG-style JSON)
{
"color": {
"light": {
"background": {
"$type": "color",
"$value": "oklch(0.990 0.005 247.9)",
"$description": "sRGB #f9fcff"
},
"text": {
"$type": "color",
"$value": "oklch(0.241 0.006 236.8)",
"$description": "sRGB #1d2022"
},
"text-muted": {
"$type": "color",
"$value": "oklch(0.459 0.008 255.5)",
"$description": "sRGB #55585c"
},
"axis": {
"$type": "color",
"$value": "oklch(0.621 0.008 247.9)",
"$description": "sRGB #83878b"
},
"gridline": {
"$type": "color",
"$value": "oklch(0.856 0.008 253.9)",
"$description": "sRGB #ccd0d5"
},
"series-1": {
"$type": "color",
"$value": "oklch(0.421 0.142 255.6)",
"$description": "sRGB #004b98"
},
"series-2": {
"$type": "color",
"$value": "oklch(0.6433 0.1323 82.79)",
"$description": "sRGB #b48400"
},
"series-3": {
"$type": "color",
"$value": "oklch(0.403 0.112 46.6)",
"$description": "sRGB #773000"
},
"series-4": {
"$type": "color",
"$value": "oklch(0.639 0.131 250.3)",
"$description": "sRGB #4790d8"
},
"series-5": {
"$type": "color",
"$value": "oklch(0.520 0.090 89.9)",
"$description": "sRGB #7d6622"
},
"series-6": {
"$type": "color",
"$value": "oklch(0.5190 0.0903 220.91)",
"$description": "sRGB #13748d"
},
"series-7": {
"$type": "color",
"$value": "oklch(0.401 0.091 319.9)",
"$description": "sRGB #5d3666"
},
"series-8": {
"$type": "color",
"$value": "oklch(0.519 0.130 9.6)",
"$description": "sRGB #a54358"
}
},
"dark": {
"background": {
"$type": "color",
"$value": "oklch(0.199 0.005 248.1)",
"$description": "sRGB #141618"
},
"text": {
"$type": "color",
"$value": "oklch(0.939 0.006 255.5)",
"$description": "sRGB #e8ebef"
},
"text-muted": {
"$type": "color",
"$value": "oklch(0.760 0.008 260.7)",
"$description": "sRGB #aeb1b6"
},
"axis": {
"$type": "color",
"$value": "oklch(0.560 0.008 248.0)",
"$description": "sRGB #717579"
},
"gridline": {
"$type": "color",
"$value": "oklch(0.334 0.008 240.1)",
"$description": "sRGB #33373a"
},
"series-1": {
"$type": "color",
"$value": "oklch(0.775 0.115 257.2)",
"$description": "sRGB #86b8ff"
},
"series-2": {
"$type": "color",
"$value": "oklch(0.600 0.166 47.2)",
"$description": "sRGB #cc5900"
},
"series-3": {
"$type": "color",
"$value": "oklch(0.839 0.129 70.9)",
"$description": "sRGB #ffbb65"
},
"series-4": {
"$type": "color",
"$value": "oklch(0.600 0.169 269.9)",
"$description": "sRGB #5a76e4"
},
"series-5": {
"$type": "color",
"$value": "oklch(0.600 0.089 359.9)",
"$description": "sRGB #ac697e"
},
"series-6": {
"$type": "color",
"$value": "oklch(0.719 0.090 340.2)",
"$description": "sRGB #cb8eb6"
},
"series-7": {
"$type": "color",
"$value": "oklch(0.6620 0.1581 120.57)",
"$description": "sRGB #86a000"
},
"series-8": {
"$type": "color",
"$value": "oklch(0.841 0.130 170.1)",
"$description": "sRGB #63e6be"
}
}
}
}Open in the studio
Take the first series colour into the picker, build a tonal scale from it, or check any pair of your own.
Open #004B98 in the OKLCH picker →Build a tonal scale →Check a pair
FAQ
- What does the minimum ΔE number mean?
- It is the smallest Euclidean distance in OKLab, multiplied by 100, between any two series colours after simulating a vision type. A difference of about 2 is barely noticeable, so the threshold of 8 means the closest pair is clearly different. It is a screening metric, not a guarantee.
- Which simulation is used?
- Machado, Oliveira and Fernandes (2009) matrices at full severity, applied through the culori library. Real-world colour vision varies, and the simulation represents the strongest case of each type.
- Why 3:1 against the background?
- WCAG 1.4.11 Non-text Contrast asks for 3:1 for parts of graphics needed to understand content. A series that fades into the page fails that, however distinct it is from other series.
- Is there a version for fewer series?
- Yes. The colorblind-friendly charts palette uses six colours and a stricter separation threshold.