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.
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
- May
- Jun
- Jul
- Aug
- Sep
- 2026
- 2025
<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
- May
- Jun
- Jul
- Aug
- Sep
- Direct
- Agents
- Online travel agencies
<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
<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
<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
<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
<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%
<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
| Part | Description |
|---|---|
| Figure | flux-chart on a figure. It has no surface of its own; place it in a card or a section. |
| Caption | A heading naming what is measured, and a summary sentence stating the takeaway. |
| Frame | The scale at the start side, gridlines across the plot, a baseline in border-strong, and the category labels below. |
| Legend | Below 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. |
| Marks | Columns, lines, bars or ring segments. data-series picks the colour; the value arrives as --flux-value, a percentage of the scale. |
API
| Name | Kind | Description |
|---|---|---|
flux-chart | class | On the figure. |
flux-chart__header | __title | __summary | class | The figcaption, its heading and the takeaway sentence. |
flux-chart__legend | __swatch | __swatch--line | __legend-value | class | The legend list, a square or line key, and a value beside a legend name. |
flux-chart__frame | __scale | __categories | class | The plot frame, its tick list (lowest first, aria-hidden) and the category labels below. |
flux-chart__columns | __bars | __column | __category | class | Columns: one list item per category, holding its label and its columns. |
flux-chart__lines | class | An 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 | __segment | class | Stacked 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--end | class | A 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__targets | class | An 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-values | class | Inside each target, aria-hidden: the category, then a dl of swatch, series name and value. |
data-flux-chart-inspect | attribute | Binds hover, focus and the arrow keys to the targets. Requires flux-components.js. |
flux-chart__rows | __label | __bar | __value | class | Ranked bars: one list item per row, with its label, bar and value. |
flux-chart__donut | __ring | __track | class | The donut and its legend, the SVG ring, and the empty ring under the segments. |
data-series | attribute | 1, 2, 3, 4 or muted, on every mark and swatch. |
--flux-value | attribute | Inline, on a column or bar: its value as a percentage of the scale’s top tick. |
States
| State | Treatment |
|---|---|
| Series colours | chart-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. |
| Comparison | chart-muted, drawn after the current series in each group, and dashed on a line. |
| Empty | Replace the plot with an Empty state that says what is missing; never draw an empty frame. |
| Inspected | A 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
| Keys | Action |
|---|---|
Tab | Moves into the inspection targets, as one stop, and out again. A chart without targets is not interactive. |
Arrow Right / Arrow Left | Moves to the next or previous category and shows its readout. |
Home / End | Moves to the first or last category. |
Escape | Hides 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.