+234 Design System

Charts

A categorical palette, a sequential ramp and a diverging ramp, each checked by the build. Twenty files currently pick their own chart colours.

The series palette

Assigned in this fixed order and never cycled. Colour follows the entity, not its rank, so a filter that drops a series must not repaint the ones that remain.

--p234-color-chart-series-1
#1565C0/#4A90D95.75:1

Blue. Series are assigned in this fixed order and never cycled: colour follows the entity, so a filter that drops a series must not repaint the survivors.

--p234-color-chart-series-2
#B08500/#AD8B2A3.39:1

Ochre.

--p234-color-chart-series-3
#B5537F/#D16A9A4.66:1

Magenta. The first three are the mutually distinct set, safe when series are compared through a legend rather than side by side.

--p234-color-chart-series-4
#2E7D32/#2F94635.13:1

Green. From here on only ADJACENT separation is guaranteed, so four or more series need direct labels or faceting.

--p234-color-chart-series-5
#6A4FA3/#9B7ED96.44:1

Purple. The last slot. A sixth series folds into Other, or the chart becomes small multiples.

How far the palette actually goes

This is the part worth reading before building a chart with more than three lines on it.

Six mutually distinct hues, inside the lightness band, each clearing 3:1 against the page, do not exist. That is measured, not asserted: searching the usable colour space, the best achievable worst pair was

3 seriesCVD 15.3normal 26.3 · comfortable
4 seriesCVD 7.4normal 17.5 · below the 8 target
5 seriesCVD 7.0normal 13.4 · below the 15 floor, a hard fail
6 seriesCVD 5.2normal 10.2 · not usable

So the system says what is true rather than shipping six and hoping:

Both rules are enforced at build time by checkSeries. The measured margins today: adjacent 9.4 in light and 8.0 in dark against a target of 8; the first three at 11.2 and 9.8 CVD, 20.2 and 19.3 for full colour vision against a floor of 15.

A chart, with the rules applied

Three series: 2px lines, recessive grid, direct labels, legend present

Petrol price by state

1400 1300 1200 1100 May Aug Nov Lagos Abuja Kano

Reported pump prices, May to November 2026. Official NBS figures lag by about four months.

HTML
<div style="max-width:40rem">
  <p class="p234-kicker" data-section="markets">Petrol price by state</p>
  <svg viewBox="0 0 560 220" role="img" aria-label="Petrol price by state, Lagos, Abuja and Kano, May to November 2026" style="width:100%;height:auto">
    <g stroke="var(--p234-color-chart-grid)" stroke-width="var(--p234-chart-grid-width)">
      <line x1="40" y1="30" x2="470" y2="30"></line>
      <line x1="40" y1="75" x2="470" y2="75"></line>
      <line x1="40" y1="120" x2="470" y2="120"></line>
      <line x1="40" y1="165" x2="470" y2="165"></line>
    </g>
    <line x1="40" y1="190" x2="470" y2="190" stroke="var(--p234-color-chart-axis)" stroke-width="1"></line>
    <g fill="var(--p234-color-ink-muted)" font-family="var(--p234-font-family-text)" font-size="11">
      <text x="34" y="34" text-anchor="end">1400</text>
      <text x="34" y="79" text-anchor="end">1300</text>
      <text x="34" y="124" text-anchor="end">1200</text>
      <text x="34" y="169" text-anchor="end">1100</text>
      <text x="40" y="205">May</text>
      <text x="255" y="205" text-anchor="middle">Aug</text>
      <text x="470" y="205" text-anchor="end">Nov</text>
    </g>
    <g fill="none" stroke-width="var(--p234-chart-line-width)" stroke-linejoin="round" stroke-linecap="round">
      <polyline points="40,170 126,150 212,118 298,96 384,62 470,40" stroke="var(--p234-color-chart-series-1)"></polyline>
      <polyline points="40,176 126,162 212,134 298,120 384,88 470,70" stroke="var(--p234-color-chart-series-2)"></polyline>
      <polyline points="40,182 126,174 212,152 298,140 384,112 470,96" stroke="var(--p234-color-chart-series-3)"></polyline>
    </g>
    <g font-family="var(--p234-font-family-text)" font-size="11" font-weight="600">
      <text x="478" y="44" fill="var(--p234-color-chart-series-1)">Lagos</text>
      <text x="478" y="74" fill="var(--p234-color-chart-series-2)">Abuja</text>
      <text x="478" y="100" fill="var(--p234-color-chart-series-3)">Kano</text>
    </g>
  </svg>
  <p style="font-family:var(--p234-type-caption-family);font-size:var(--p234-type-caption-size);color:var(--p234-color-ink-subtle);margin-top:var(--p234-space-3)">Reported pump prices, May to November 2026. Official NBS figures lag by about four months.</p>
</div>

Note what the example does not do: it puts no number on every point, it does not colour the axis labels to match the series, and the series names sit at the end of each line so identity never depends on colour alone.

Chart furniture

--p234-color-chart-axis
#8c887e/#6b685f3.54:1

Axis line and ticks. Non-text, needs 3:1.

--p234-color-chart-grid
#ebe8e1/#2827221.22:1

Grid lines, deliberately recessive. Not gated at 3:1 because a grid that competes with the data is a worse chart.

--p234-color-chart-reference
#6e6b62/#9b978d5.33:1

Dashed reference lines: a target, an official figure, a prior period.

--p234-color-chart-tooltip-bg
#1a1a18/#2a2a2617.43:1
--p234-color-chart-tooltip-ink
#ffffff/#f2f0ea1.00:1
--p234-color-chart-tooltip-border
#3f3d38/#4a484010.85:1

The grid is the one colour in the system deliberately not gated at 3:1. A grid that competes with the data is a worse chart, so it is allowed to be faint.

Mark specs

--p234-chart-line-width2px
--p234-chart-grid-width1px
--p234-chart-marker-size8pxMinimum hit and read size for a point marker.
--p234-chart-area-opacity0.14Fill under a line. Low enough that an overlapping pair stays legible.
--p234-chart-mark-gap2pxSurface-coloured gap between adjacent bars and stacked segments.
--p234-chart-radius-end4pxRounded data end on a bar, anchored to the baseline. The baseline end stays square.

Sequential, for magnitude

One hue, light to dark. This is what the Nigeria choropleth reads, and what any "how much" map or heatmap uses. Never a rainbow: a rainbow implies categories where there is only quantity.

--p234-color-chart-seq-1
#8CB2DD/#36618C2.20:1

Sequential ramp for magnitude, one hue light to dark. This is what the Nigeria choropleth reads. Never a rainbow.

--p234-color-chart-seq-2
#5F8FC4/#4A7FB03.38:1
--p234-color-chart-seq-3
#3F6EA8/#5E9CD15.24:1
--p234-color-chart-seq-4
#27507F/#86B9E38.27:1
--p234-color-chart-seq-5
#123455/#B3D6F212.74:1

The build checks that lightness is monotonic and that each step is at least 0.06 apart, so the steps stay tellable from one another when the map shrinks to phone width.

Diverging, for polarity

Two poles and a neutral midpoint, for data where the sign matters as much as the size: a swing, a margin, a change against a prior period.

--p234-color-chart-div-1
#8A6D00/#AD8B2A4.92:1

Diverging ramp for polarity: warm pole, neutral midpoint, cool pole. For swings and margins, where the sign matters as much as the size.

--p234-color-chart-div-2
#C9AE6E/#7E6A322.15:1
--p234-color-chart-div-3
#EDEBE6/#34322E1.19:1

The neutral midpoint. Never a hue: a coloured midpoint invents a category that is not in the data.

--p234-color-chart-div-4
#6E9FD4/#3E6B962.77:1
--p234-color-chart-div-5
#1D4F85/#86B9E38.37:1

The midpoint is never a hue. A test fails the build if it picks up chroma, because a coloured midpoint invents a category that is not in the data.

Rules

Dark is chosen, not flipped

Every dark step was re-selected against the dark page and re-validated from scratch. An automatic lightness flip puts colours outside the dark band, which is narrower than the light one: 0.48 to 0.67 against 0.43 to 0.77.

One finding worth recording: teal is not available to this system. At every lightness that clears the band, teal sits below the 0.10 chroma floor and reads as grey, so it was dropped from the hue families rather than shipped as a colour nobody can name.