Flux 2.0

Component

Chart

Columns, stacked columns, lines, areas, two scales, ranked bars and a donut, drawn from theme roles, with a readout on hover or focus.

Plain-text source, for agents and clipboard use: /components/source/chart.txt

When to use it

Shows a trend, a comparison or a share that a table of numbers would make someone work out. Each chart is built from lists and inline SVG rather than a library, so every mark takes a theme role and re-colours with the theme. Series take --flux-chart-1 to -4 in order, and --flux-chart-muted for the comparison, such as last year. The caption states the takeaway in words, so the chart is evidence for a sentence rather than a puzzle.

Not this
Not for one figure; use a Metric. Not for exact values someone will copy or act on; use a Table, or put one beside the chart. Not for more than four series; split the chart or rank the values as bars. Not for a matrix of values across two dimensions; use a Heatmap. Not for decoration on a metric, such as a sparkline in every tile.

Examples

Columns, compared with last year

Two series per month. The values are in visually hidden text inside each column, so a screen reader hears "Jul, 2026: 281, 2025: 248".

Bookings by month

Every month is ahead of last year. July is the peak, at 281.

  • Apr 2026: 142 2025: 128
  • May 2026: 188 2025: 171
  • Jun 2026: 236 2025: 219
  • Jul 2026: 281 2025: 248
  • Aug 2026: 264 2025: 259
  • Sep 2026: 212 2025: 189
  • 2026
  • 2025
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Bookings by month</h3>
    <p class="flux-chart__summary">Every month is ahead of last year. July is the peak, at 281.</p>
  </figcaption>
  <div class="flux-chart__frame">
    <ol class="flux-chart__scale" aria-hidden="true">
      <li><span>0</span></li>
      <li><span>100</span></li>
      <li><span>200</span></li>
      <li><span>300</span></li>
    </ol>
    <ul class="flux-chart__columns">
      <li>
        <span class="flux-chart__category">Apr</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 47.33%"><span class="flux-visually-hidden">2026: 142</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 42.67%"><span class="flux-visually-hidden">2025: 128</span></span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">May</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 62.67%"><span class="flux-visually-hidden">2026: 188</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 57%"><span class="flux-visually-hidden">2025: 171</span></span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Jun</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 78.67%"><span class="flux-visually-hidden">2026: 236</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 73%"><span class="flux-visually-hidden">2025: 219</span></span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Jul</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 93.67%"><span class="flux-visually-hidden">2026: 281</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 82.67%"><span class="flux-visually-hidden">2025: 248</span></span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Aug</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 88%"><span class="flux-visually-hidden">2026: 264</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 86.33%"><span class="flux-visually-hidden">2025: 259</span></span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Sep</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__column" data-series="1" style="--flux-value: 70.67%"><span class="flux-visually-hidden">2026: 212</span></span>
          <span class="flux-chart__column" data-series="muted" style="--flux-value: 63%"><span class="flux-visually-hidden">2025: 189</span></span>
        </span>
      </li>
    </ul>
  </div>
  <ul class="flux-chart__legend">
    <li><span class="flux-chart__swatch" data-series="1"></span>2026</li>
    <li><span class="flux-chart__swatch" data-series="muted"></span>2025</li>
  </ul>
</figure>

Stacked columns

Parts of a total per category. The stack takes the total as a share of the scale, and each segment its part as a share of the stack, first series on the baseline. The total is labelled above, and each part is in hidden text: "Jul, Total: 281, Direct: 135, Agents: 82, Online travel agencies: 64".

Bookings by month and channel

Direct bookings are about half of every month, and July is the peak in every channel.

  • Apr Total: 142 Direct: 70 Agents: 42 Online travel agencies: 30
  • May Total: 188 Direct: 90 Agents: 55 Online travel agencies: 43
  • Jun Total: 236 Direct: 112 Agents: 70 Online travel agencies: 54
  • Jul Total: 281 Direct: 135 Agents: 82 Online travel agencies: 64
  • Aug Total: 264 Direct: 128 Agents: 76 Online travel agencies: 60
  • Sep Total: 212 Direct: 101 Agents: 62 Online travel agencies: 49
  • Direct
  • Agents
  • Online travel agencies
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Bookings by month and channel</h3>
    <p class="flux-chart__summary">Direct bookings are about half of every month, and July is the peak in every channel.</p>
  </figcaption>
  <div class="flux-chart__frame">
    <ol class="flux-chart__scale" aria-hidden="true">
      <li><span>0</span></li>
      <li><span>100</span></li>
      <li><span>200</span></li>
      <li><span>300</span></li>
    </ol>
    <ul class="flux-chart__columns">
      <li>
        <span class="flux-chart__category">Apr</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 47.33%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>142</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 49.3%"><span class="flux-visually-hidden">Direct: 70</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 29.58%"><span class="flux-visually-hidden">Agents: 42</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 21.13%"><span class="flux-visually-hidden">Online travel agencies: 30</span></span>
          </span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">May</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 62.67%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>188</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 47.87%"><span class="flux-visually-hidden">Direct: 90</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 29.26%"><span class="flux-visually-hidden">Agents: 55</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 22.87%"><span class="flux-visually-hidden">Online travel agencies: 43</span></span>
          </span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Jun</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 78.67%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>236</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 47.46%"><span class="flux-visually-hidden">Direct: 112</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 29.66%"><span class="flux-visually-hidden">Agents: 70</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 22.88%"><span class="flux-visually-hidden">Online travel agencies: 54</span></span>
          </span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Jul</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 93.67%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>281</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 48.04%"><span class="flux-visually-hidden">Direct: 135</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 29.18%"><span class="flux-visually-hidden">Agents: 82</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 22.78%"><span class="flux-visually-hidden">Online travel agencies: 64</span></span>
          </span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Aug</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 88%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>264</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 48.48%"><span class="flux-visually-hidden">Direct: 128</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 28.79%"><span class="flux-visually-hidden">Agents: 76</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 22.73%"><span class="flux-visually-hidden">Online travel agencies: 60</span></span>
          </span>
        </span>
      </li>
      <li>
        <span class="flux-chart__category">Sep</span>
        <span class="flux-chart__bars">
          <span class="flux-chart__stack" style="--flux-value: 70.67%">
            <span class="flux-chart__value"><span class="flux-visually-hidden">Total: </span>212</span>
            <span class="flux-chart__segment" data-series="1" style="--flux-value: 47.64%"><span class="flux-visually-hidden">Direct: 101</span></span>
            <span class="flux-chart__segment" data-series="2" style="--flux-value: 29.25%"><span class="flux-visually-hidden">Agents: 62</span></span>
            <span class="flux-chart__segment" data-series="3" style="--flux-value: 23.11%"><span class="flux-visually-hidden">Online travel agencies: 49</span></span>
          </span>
        </span>
      </li>
    </ul>
  </div>
  <ul class="flux-chart__legend">
    <li><span class="flux-chart__swatch" data-series="1"></span>Direct</li>
    <li><span class="flux-chart__swatch" data-series="2"></span>Agents</li>
    <li><span class="flux-chart__swatch" data-series="3"></span>Online travel agencies</li>
  </ul>
</figure>

Line

A trend over time, on the same frame. Points sit at the centre of each month, with y as a share of the scale. The comparison is dashed as well as muted, so it does not depend on colour.

Occupancy

Seats sold as a share of seats offered. August peaked at 91%, five points above last year.

  • 2026
  • 2025
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Occupancy</h3>
    <p class="flux-chart__summary">Seats sold as a share of seats offered. August peaked at 91%, five points above last year.</p>
  </figcaption>
  <div class="flux-chart__frame">
    <ol class="flux-chart__scale" aria-hidden="true">
      <li><span>0%</span></li>
      <li><span>25%</span></li>
      <li><span>50%</span></li>
      <li><span>75%</span></li>
      <li><span>100%</span></li>
    </ol>
    <svg class="flux-chart__lines" viewBox="0 0 100 100" preserveAspectRatio="none" role="img" aria-label="Occupancy by month, April to September. 2026: Apr 58%, May 66%, Jun 79%, Jul 88%, Aug 91%, Sep 74%. 2025: Apr 52%, May 61%, Jun 72%, Jul 84%, Aug 86%, Sep 70%.">
      <polyline data-series="muted" points="8.33,48 25,39 41.67,28 58.33,16 75,14 91.67,30" />
      <polyline data-series="1" points="8.33,42 25,34 41.67,21 58.33,12 75,9 91.67,26" />
    </svg>
    <ol class="flux-chart__categories" aria-hidden="true">
      <li>Apr</li>
      <li>May</li>
      <li>Jun</li>
      <li>Jul</li>
      <li>Aug</li>
      <li>Sep</li>
    </ol>
  </div>
  <ul class="flux-chart__legend">
    <li><span class="flux-chart__swatch flux-chart__swatch--line" data-series="1"></span>2026</li>
    <li><span class="flux-chart__swatch flux-chart__swatch--line" data-series="muted"></span>2025</li>
  </ul>
</figure>

Area, cumulative

A running total, so the line only rises. A polygon closes the current series down to the baseline and fills it as a tint; the line on top carries the colour. The comparison stays a dashed line without an area, so the two never overlap as fills.

Bookings on the books

The season reached 1,323 bookings by September, 109 more than last year.

  • 2026
  • 2025
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Bookings on the books</h3>
    <p class="flux-chart__summary">The season reached 1,323 bookings by September, 109 more than last year.</p>
  </figcaption>
  <div class="flux-chart__frame">
    <ol class="flux-chart__scale" aria-hidden="true">
      <li><span>0</span></li>
      <li><span>500</span></li>
      <li><span>1,000</span></li>
      <li><span>1,500</span></li>
    </ol>
    <svg class="flux-chart__lines" viewBox="0 0 100 100" preserveAspectRatio="none" role="img" aria-label="Cumulative bookings, April to September. 2026: Apr 142, May 330, Jun 566, Jul 847, Aug 1111, Sep 1323. 2025: Apr 128, May 299, Jun 518, Jul 766, Aug 1025, Sep 1214.">
      <polygon data-series="1" points="8.33,100 8.33,90.53 25,78 41.67,62.27 58.33,43.53 75,25.93 91.67,11.8 91.67,100" />
      <polyline data-series="muted" points="8.33,91.47 25,80.07 41.67,65.47 58.33,48.93 75,31.67 91.67,19.07" />
      <polyline data-series="1" points="8.33,90.53 25,78 41.67,62.27 58.33,43.53 75,25.93 91.67,11.8" />
    </svg>
    <ol class="flux-chart__categories" aria-hidden="true">
      <li>Apr</li>
      <li>May</li>
      <li>Jun</li>
      <li>Jul</li>
      <li>Aug</li>
      <li>Sep</li>
    </ol>
  </div>
  <ul class="flux-chart__legend">
    <li><span class="flux-chart__swatch" data-series="1"></span>2026</li>
    <li><span class="flux-chart__swatch flux-chart__swatch--line" data-series="muted"></span>2025</li>
  </ul>
</figure>

Columns and a line on two scales, with inspection

Two measures in different units, revenue against margin. flux-chart__frame--dual adds a scale at the end side; both scales have the same number of ticks, so they label the same gridlines, and the legend says which scale each series reads against. Hover a month, or Tab into the plot and use the arrow keys: a guide and a readout show that month’s values. The targets carry the values in words, so the marks are hidden from assistive technology.

Revenue and margin

Revenue peaked in July at €438k, and margin rose with it, to 21%.

  • Revenue, left scale
  • Margin, right scale
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Revenue and margin</h3>
    <p class="flux-chart__summary">Revenue peaked in July at €438k, and margin rose with it, to 21%.</p>
  </figcaption>
  <div class="flux-chart__frame flux-chart__frame--dual">
    <ol class="flux-chart__scale" aria-hidden="true">
      <li><span>€0</span></li>
      <li><span>€150k</span></li>
      <li><span>€300k</span></li>
      <li><span>€450k</span></li>
    </ol>
    <ol class="flux-chart__scale flux-chart__scale--end" aria-hidden="true">
      <li><span>0%</span></li>
      <li><span>10%</span></li>
      <li><span>20%</span></li>
      <li><span>30%</span></li>
    </ol>
    <ul class="flux-chart__columns" aria-hidden="true">
      <li>
        <span class="flux-chart__category">Apr</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 69.33%"></span></span>
      </li>
      <li>
        <span class="flux-chart__category">May</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 79.11%"></span></span>
      </li>
      <li>
        <span class="flux-chart__category">Jun</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 89.11%"></span></span>
      </li>
      <li>
        <span class="flux-chart__category">Jul</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 97.33%"></span></span>
      </li>
      <li>
        <span class="flux-chart__category">Aug</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 93.33%"></span></span>
      </li>
      <li>
        <span class="flux-chart__category">Sep</span>
        <span class="flux-chart__bars"><span class="flux-chart__column" data-series="1" style="--flux-value: 76.44%"></span></span>
      </li>
    </ul>
    <svg class="flux-chart__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
      <polyline data-series="2" points="8.33,53.33 25,46.67 41.67,36.67 58.33,30 75,40 91.67,50" />
    </svg>
    <ol class="flux-chart__targets" data-flux-chart-inspect aria-label="Revenue and margin by month">
      <li tabindex="0" aria-label="Apr. Revenue: €312k, Margin: 14%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">Apr</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€312k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>14%</dd></div>
          </dl>
        </div>
      </li>
      <li tabindex="-1" aria-label="May. Revenue: €356k, Margin: 16%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">May</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€356k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>16%</dd></div>
          </dl>
        </div>
      </li>
      <li tabindex="-1" aria-label="Jun. Revenue: €401k, Margin: 19%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">Jun</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€401k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>19%</dd></div>
          </dl>
        </div>
      </li>
      <li tabindex="-1" aria-label="Jul. Revenue: €438k, Margin: 21%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">Jul</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€438k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>21%</dd></div>
          </dl>
        </div>
      </li>
      <li tabindex="-1" aria-label="Aug. Revenue: €420k, Margin: 18%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">Aug</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€420k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>18%</dd></div>
          </dl>
        </div>
      </li>
      <li tabindex="-1" aria-label="Sep. Revenue: €344k, Margin: 15%">
        <div class="flux-chart__readout" aria-hidden="true">
          <p class="flux-chart__readout-title">Sep</p>
          <dl class="flux-chart__readout-values">
            <div><dt><span class="flux-chart__swatch" data-series="1"></span>Revenue</dt><dd>€344k</dd></div>
            <div><dt><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin</dt><dd>15%</dd></div>
          </dl>
        </div>
      </li>
    </ol>
  </div>
  <ul class="flux-chart__legend">
    <li><span class="flux-chart__swatch" data-series="1"></span>Revenue, left scale</li>
    <li><span class="flux-chart__swatch flux-chart__swatch--line" data-series="2"></span>Margin, right scale</li>
  </ul>
</figure>

Ranked bars

One series, largest first, each value labelled at the end of its row. The bars scale to the largest value, so no axis is needed.

Revenue by tour, September

Golden Circle earns more than the two smallest tours together.

  • Golden Circle €412k
  • South Coast €318k
  • Snæfellsnes €196k
  • Westfjords €88k
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Revenue by tour, September</h3>
    <p class="flux-chart__summary">Golden Circle earns more than the two smallest tours together.</p>
  </figcaption>
  <ul class="flux-chart__rows">
    <li>
      <span class="flux-chart__label">Golden Circle</span>
      <span class="flux-chart__bar" data-series="1" style="--flux-value: 100%"></span>
      <span class="flux-chart__value">€412k</span>
    </li>
    <li>
      <span class="flux-chart__label">South Coast</span>
      <span class="flux-chart__bar" data-series="1" style="--flux-value: 77.18%"></span>
      <span class="flux-chart__value">€318k</span>
    </li>
    <li>
      <span class="flux-chart__label">Snæfellsnes</span>
      <span class="flux-chart__bar" data-series="1" style="--flux-value: 47.57%"></span>
      <span class="flux-chart__value">€196k</span>
    </li>
    <li>
      <span class="flux-chart__label">Westfjords</span>
      <span class="flux-chart__bar" data-series="1" style="--flux-value: 21.36%"></span>
      <span class="flux-chart__value">€88k</span>
    </li>
  </ul>
</figure>

Donut

Shares of one whole. The ring’s circumference is 100, so each segment’s dash is its percentage and its offset is the total of the segments before it. The legend carries the values.

Bookings by channel

Almost half of this season’s 1,322 bookings came direct.

  • Direct48%
  • Agents27%
  • Online travel agencies15%
  • Other10%
html
<figure class="flux-chart">
  <figcaption class="flux-chart__header">
    <h3 class="flux-chart__title">Bookings by channel</h3>
    <p class="flux-chart__summary">Almost half of this season’s 1,322 bookings came direct.</p>
  </figcaption>
  <div class="flux-chart__donut">
    <svg class="flux-chart__ring" viewBox="0 0 42 42" aria-hidden="true">
      <circle class="flux-chart__track" cx="21" cy="21" r="15.9155" />
      <circle data-series="1" cx="21" cy="21" r="15.9155" stroke-dasharray="48 52" stroke-dashoffset="0" />
      <circle data-series="2" cx="21" cy="21" r="15.9155" stroke-dasharray="27 73" stroke-dashoffset="-48" />
      <circle data-series="3" cx="21" cy="21" r="15.9155" stroke-dasharray="15 85" stroke-dashoffset="-75" />
      <circle data-series="muted" cx="21" cy="21" r="15.9155" stroke-dasharray="10 90" stroke-dashoffset="-90" />
    </svg>
    <ul class="flux-chart__legend">
      <li><span class="flux-chart__swatch" data-series="1"></span>Direct<span class="flux-chart__legend-value">48%</span></li>
      <li><span class="flux-chart__swatch" data-series="2"></span>Agents<span class="flux-chart__legend-value">27%</span></li>
      <li><span class="flux-chart__swatch" data-series="3"></span>Online travel agencies<span class="flux-chart__legend-value">15%</span></li>
      <li><span class="flux-chart__swatch" data-series="muted"></span>Other<span class="flux-chart__legend-value">10%</span></li>
    </ul>
  </div>
</figure>

Anatomy

PartDescription
Figureflux-chart on a figure. It has no surface of its own; place it in a card or a section.
CaptionA heading naming what is measured, and a summary sentence stating the takeaway.
FrameThe scale at the start side, gridlines across the plot, a baseline in border-strong, and the category labels below.
LegendBelow the plot, a swatch and name per series. Beside the ring on a donut, with the values. Omit it for a single series the title already names.
MarksColumns, lines, bars or ring segments. data-series picks the colour; the value arrives as --flux-value, a percentage of the scale.

API

NameKindDescription
flux-chartclassOn the figure.
flux-chart__header | __title | __summaryclassThe figcaption, its heading and the takeaway sentence.
flux-chart__legend | __swatch | __swatch--line | __legend-valueclassThe legend list, a square or line key, and a value beside a legend name.
flux-chart__frame | __scale | __categoriesclassThe plot frame, its tick list (lowest first, aria-hidden) and the category labels below.
flux-chart__columns | __bars | __column | __categoryclassColumns: one list item per category, holding its label and its columns.
flux-chart__linesclassAn SVG with viewBox="0 0 100 100" and preserveAspectRatio="none", holding one polyline per series, role="img" and an aria-label with the values. A polygon in it is an area: the line’s points closed down to y 100.
flux-chart__stack | __segmentclassStacked columns: the stack in place of a column, with --flux-value as the total’s share of the scale, and one segment per series with --flux-value as its share of the stack.
flux-chart__frame--dual | __scale--endclassA second scale at the end side of the frame, for a series in another unit. Give it the same number of ticks as the start scale.
flux-chart__targetsclassAn ol over the plot with data-flux-chart-inspect and an aria-label: one li per category, tabindex="0" on the first and "-1" on the rest, each with an aria-label stating its values.
flux-chart__readout | __readout-title | __readout-valuesclassInside each target, aria-hidden: the category, then a dl of swatch, series name and value.
data-flux-chart-inspectattributeBinds hover, focus and the arrow keys to the targets. Requires flux-components.js.
flux-chart__rows | __label | __bar | __valueclassRanked bars: one list item per row, with its label, bar and value.
flux-chart__donut | __ring | __trackclassThe donut and its legend, the SVG ring, and the empty ring under the segments.
data-seriesattribute1, 2, 3, 4 or muted, on every mark and swatch.
--flux-valueattributeInline, on a column or bar: its value as a percentage of the scale’s top tick.

States

StateTreatment
Series colourschart-1 teal, chart-2 purple, chart-3 magenta, chart-4 ochre, and chart-muted grey for the comparison. Each clears 3:1 against every layer in light, dark and deep.
Comparisonchart-muted, drawn after the current series in each group, and dashed on a line.
EmptyReplace the plot with an Empty state that says what is missing; never draw an empty frame.
InspectedA hairline guide in border-strong through the category, and its readout on surface-overlay. The readout opens towards the start for the last two categories, so it stays over the plot.

Keyboard

KeysAction
TabMoves into the inspection targets, as one stop, and out again. A chart without targets is not interactive.
Arrow Right / Arrow LeftMoves to the next or previous category and shows its readout.
Home / EndMoves to the first or last category.
EscapeHides the readout; moving again shows it.

Accessibility

  • The summary sentence states the takeaway, so nobody has to read the shape to get the point.
  • Every value is available as text: hidden text in columns, visible labels on bars and in the donut legend, and the aria-label on a line chart. Scales are aria-hidden, because the values are already read.
  • Colour is never the only signal: series are named in the legend, ordered the same way in every group, and the comparison line is dashed.
  • Chart roles clear 3:1 against every layer, as WCAG 1.4.11 requires for graphics needed to understand the content.
  • An inspection target is announced by its aria-label, such as "Jul. Revenue: €438k, Margin: 21%". The readout repeats it visually and is aria-hidden, and the marks under it are aria-hidden too, so nothing is read twice.
  • The readout never holds the only copy of a value someone needs. If people will compare exact figures, put a Table beside the chart.

Tokens

The tier-3 and tier-2 tokens this component binds to. Change the token, not the component.

  • --flux-chart-1 | -2 | -3 | -4 | -muted (Tier 2)
  • --flux-chart-gap | -plot-height | -scale-width | -headroom | -column-gap | -bar-width | -row-bar-height
  • --flux-chart-grid | -axis | -label | -track | -legend-gap | -swatch-size
  • --flux-chart-line-width | -line-dash | -donut-size | -donut-stroke
  • --flux-chart-area-opacity | -stack-gap | -guide
  • --flux-chart-readout-gap | -readout-padding-x | -readout-padding-y | -readout-bg | -readout-border | -readout-shadow

Do and don’t

Do

  • State the takeaway in the summary sentence.
  • Start column and bar scales at zero.
  • Use series colours in order, and the muted colour for the comparison.
  • Place a chart below the metric row at full width, as the Dashboards pattern describes.

Don’t

  • Do not bring in a chart library’s palette, gradients or animations.
  • Do not use product accents or status colours as series; a red series reads as an error.
  • Do not draw more than four series, or a donut with more than five segments.
  • Do not put two series in the same unit on two scales; one scale tells the truth about their sizes. A second scale is for a second unit, and its range must start at zero.
  • Do not stack a comparison series such as last year. Stacks are parts of one total.
  • Do not use a chart for a single number. Use a Metric.