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.

Light theme: 11 pairs
PairWCAG ratioTargetNormal AA / AAALarge AA / AAAAPCA LcAPCA tierCheck
Chart text on background#1d2022 on #f9fcff15.91:1AA 4.5:1Pass / PassPass / Pass+101.3Lc 75: body textOpen in checker
Axis labels on background#55585c on #f9fcff6.94:1AA 4.5:1Pass / FailPass / Pass+82.8Lc 75: body textOpen in checker
Axis line on background#83878b on #f9fcff3.51:13:1 (WCAG 1.4.11)Fail / FailPass / Fail+61.8Lc 60: larger content textOpen in checker
Series 1 on background#004b98 on #f9fcff8.29:13:1 (WCAG 1.4.11)Pass / PassPass / Pass+86.6Lc 75: body textOpen in checker
Series 2 on background#b48400 on #f9fcff3.26:13:1 (WCAG 1.4.11)Fail / FailPass / Fail+58.8Lc 45: headlines and large textOpen in checker
Series 3 on background#773000 on #f9fcff9.24:13:1 (WCAG 1.4.11)Pass / PassPass / Pass+89.3Lc 75: body textOpen in checker
Series 4 on background#4790d8 on #f9fcff3.26:13:1 (WCAG 1.4.11)Fail / FailPass / Fail+58.7Lc 45: headlines and large textOpen in checker
Series 5 on background#7d6622 on #f9fcff5.37:13:1 (WCAG 1.4.11)Pass / FailPass / Pass+75.5Lc 75: body textOpen in checker
Series 6 on background#13748d on #f9fcff5.21:13:1 (WCAG 1.4.11)Pass / FailPass / Pass+74.3Lc 60: larger content textOpen in checker
Series 7 on background#5d3666 on #f9fcff9.33:13:1 (WCAG 1.4.11)Pass / PassPass / Pass+90.0Lc 75: body textOpen in checker
Series 8 on background#a54358 on #f9fcff5.75:13:1 (WCAG 1.4.11)Pass / FailPass / Pass+77.0Lc 75: body textOpen in checker
Dark theme: 11 pairs
PairWCAG ratioTargetNormal AA / AAALarge AA / AAAAPCA LcAPCA tierCheck
Chart text on background#e8ebef on #14161815.16:1AA 4.5:1Pass / PassPass / Pass−93.7Lc 75: body textOpen in checker
Axis labels on background#aeb1b6 on #1416188.43:1AA 4.5:1Pass / PassPass / Pass−59.1Lc 45: headlines and large textOpen in checker
Axis line on background#717579 on #1416183.90:13:1 (WCAG 1.4.11)Fail / FailPass / Fail−28.5Below Lc 30Open in checker
Series 1 on background#86b8ff on #1416188.90:13:1 (WCAG 1.4.11)Pass / PassPass / Pass−61.9Lc 60: larger content textOpen in checker
Series 2 on background#cc5900 on #1416184.31:13:1 (WCAG 1.4.11)Fail / FailPass / Fail−32.6Lc 30: non-text and incidentalOpen in checker
Series 3 on background#ffbb65 on #14161810.83:13:1 (WCAG 1.4.11)Pass / PassPass / Pass−72.6Lc 60: larger content textOpen in checker
Series 4 on background#5a76e4 on #1416184.44:13:1 (WCAG 1.4.11)Fail / FailPass / Fail−33.1Lc 30: non-text and incidentalOpen in checker
Series 5 on background#ac697e on #1416184.38:13:1 (WCAG 1.4.11)Fail / FailPass / Fail−32.5Lc 30: non-text and incidentalOpen in checker
Series 6 on background#cb8eb6 on #1416186.98:13:1 (WCAG 1.4.11)Pass / FailPass / Pass−50.4Lc 45: headlines and large textOpen in checker
Series 7 on background#86a000 on #1416186.08:13:1 (WCAG 1.4.11)Pass / FailPass / Pass−44.7Lc 30: non-text and incidentalOpen in checker
Series 8 on background#63e6be on #14161811.74:13:1 (WCAG 1.4.11)Pass / PassPass / Pass−77.4Lc 75: body textOpen 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).

Light chart theme
VisionSimulated coloursMin ΔE-OK ×100Closest pair
Normal vision12.9 (≥ 8)1 and 6
Protanopia8.0 (≥ 8)7 and 8
Deuteranopia8.7 (≥ 8)5 and 8
Tritanopia9.2 (≥ 8)5 and 8
Dark chart theme
VisionSimulated coloursMin ΔE-OK ×100Closest pair
Normal vision12.3 (≥ 8)5 and 6
Protanopia10.9 (≥ 8)3 and 8
Deuteranopia9.8 (≥ 8)6 and 8
Tritanopia9.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.

← All accessible palettes