NS Design System
v3.0.0 · 257 tokens

152Introduction

Data visualization in this system: what earns a chart, the anatomy every chart shares, and the machine-verified palette. One rule above all — the data is the only thing allowed to be loud.

Form first — is it even a chart?

Chart it when

  • Change over time — line or column
  • Comparison across entities — bars
  • Parts of one whole (≤ 5 parts) — donut or stacked bar
  • Magnitude across a scale — sequential fills

Not a chart

  • One headline number — a stat tile (see Stat), not a one-bar chart
  • Two values — write them in a sentence
  • Eight-plus series — fold into “Other”, facet, or rethink the question

Anatomy — every chart is these parts

Frame with title + mono subtitle · legend (always, for two or more series; never for one) · recessive hairline gridlines · mono axis labels · thin marks in slot colors · tooltip on hover/focus. Hover a column below — the whole column is the hit target, not just the bar.

Course completions weekly · last 6 weeks
  • Admin track
  • Developer track
0255075100
W1 · admin 38 · dev 24
W2 · admin 52 · dev 30
W3 · admin 46 · dev 41
W4 · admin 64 · dev 38
W5 · admin 58 · dev 52
W6 · admin 82 · dev 61
W1W2W3W4W5W6
markup
<figure class="ns-chart">
  <div class="ns-chart__head">
    <span class="ns-chart__title">Course completions</span>
    <span class="ns-chart__sub">weekly · last 6 weeks</span>
  </div>
  <ul class="ns-chart__legend">
    <li class="ns-chart__key" data-c="1"><span class="ns-chart__swatch"></span>Admin track</li>
    <li class="ns-chart__key" data-c="2"><span class="ns-chart__swatch"></span>Developer track</li>
  </ul>
  <div class="ns-chart__frame">
    <div class="ns-chart__y"><span>0</span><span>25</span><span>50</span><span>75</span><span>100</span></div>
    <div class="ns-chart__plot">
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:38%">W1 · admin 38 · dev 24</span>
        <div class="ns-chart__bar" data-c="1" style="--v:38%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:24%"></div>
      </div>
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:52%">W2 · admin 52 · dev 30</span>
        <div class="ns-chart__bar" data-c="1" style="--v:52%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:30%"></div>
      </div>
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:46%">W3 · admin 46 · dev 41</span>
        <div class="ns-chart__bar" data-c="1" style="--v:46%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:41%"></div>
      </div>
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:64%">W4 · admin 64 · dev 38</span>
        <div class="ns-chart__bar" data-c="1" style="--v:64%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:38%"></div>
      </div>
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:58%">W5 · admin 58 · dev 52</span>
        <div class="ns-chart__bar" data-c="1" style="--v:58%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:52%"></div>
      </div>
      <div class="ns-chart__col ns-chart__col--group" tabindex="0">
        <span class="ns-chart__tip" style="--v:82%">W6 · admin 82 · dev 61</span>
        <div class="ns-chart__bar" data-c="1" style="--v:82%"></div>
        <div class="ns-chart__bar" data-c="2" style="--v:61%"></div>
      </div>
    </div>
  <div class="ns-chart__x"><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span></div>
  </div>
</figure>

Color has four jobs — and only four

Categorical answers “which series is this?” — seven fixed slots, assigned 1→7 in order, never cycled or reordered; slot 1 is always brand blue. Filtering a series out never repaints the survivors: color follows the entity.

--chart-cat-1 #0176d3 flips
--chart-cat-2 #d1490d flips
--chart-cat-3 #2e844a flips
--chart-cat-4 #9c3ad9 flips
--chart-cat-5 #0f9ba8 flips
--chart-cat-6 #a8890a flips
--chart-cat-7 #d6247a flips

Sequential answers “how much?” — one hue, light→dark, four honest steps.

--chart-seq-1 #5aa9f7 flips
--chart-seq-2 #0176d3 flips
--chart-seq-3 #0b5cab flips
--chart-seq-4 #03386b flips

Diverging answers “which side of the baseline?” — cool arm above, warm arm below, a true neutral at the midpoint (never a hue).

--chart-div-cool-3 #03386b flips
--chart-div-cool-2 #0176d3 flips
--chart-div-cool-1 #5aa9f7 flips
--chart-div-mid #c9c7c5 flips
--chart-div-warm-1 #e3a06c flips
--chart-div-warm-2 #d1490d flips
--chart-div-warm-3 #7a2a06 flips

Status is reserved. Success/warning/error never paint a series, and a series color never means “bad”. Conditional formatting happens on TEXT with status ink — a delta beside the value — never by recoloring the mark:

1,284
Enrollments ▲ 12%
64%
Completion ▼ 3%

The six rules, verified in CI

  • Slots 1→7 in fixed order — never cycled, never generated
  • No 8th hue — fold into “Other” or facet
  • Status colors never paint a series
  • Text wears ink tokens, never the series color
  • Sequential = one hue; diverging = neutral midpoint
  • Dark mode is re-picked steps, not a mechanical flip — npm run check:palette proves all of it

Data Visualization

Seven categorical slots, CVD-verified in CI — brand blue is slot 1 and status colors are reserved

open ↗