Navigation

v0.0.4
Data display

Gauge

A reading on a dial: an arc that fills as far as the value has come.

On this page

A gauge is a reading on a dial: two thirds of a ring, open at the foot, that fills with ink as far as the value has come. The figure stands in the middle and one word under it, between the two ends of the arc. It is an element with role="meter", so the reading is written for people and the stylesheet draws it. There is no script and no SVG.

Gauge

<div class="card max-w(480) mx-auto">  <div class="flex flex-wrap justify-around gap-md">    <div class="gauge" role="meter" aria-label="CPU" aria-valuenow="38" style="--gauge-value: 38">      <span class="gauge-value">38%</span>      <span class="gauge-label">CPU</span>    </div>    <div class="gauge" role="meter" aria-label="Memory" aria-valuenow="72" style="--gauge-value: 72">      <span class="gauge-value">72%</span>      <span class="gauge-label">Memory</span>    </div>    <div class="gauge" role="meter" aria-label="Disk" aria-valuenow="91" style="--gauge-value: 91">      <span class="gauge-value">91%</span>      <span class="gauge-label">Disk</span>    </div>  </div></div>

#How the arc gets its length

The reading is aria-valuenow, between aria-valuemin and aria-valuemax, or 0 and 100 when they are left out. Where the browser supports typed attr(), the gauge reads those attributes itself, so the number is written once. Elsewhere it reads --gauge-value, and --gauge-min and --gauge-max for another range: until typed attr() is in every engine, write both, as the examples here do.

The track and the fill each end in a round cap, and the fill eases to a new reading. An empty gauge is an empty track: the caps have no size at nothing.

A range of its own

<div class="gauge" role="meter" aria-label="Engine speed" aria-valuenow="4200" aria-valuemin="0" aria-valuemax="8000" style="--gauge-value: 4200; --gauge-max: 8000">  <span class="gauge-value">4,200</span>  <span class="gauge-label">rpm</span></div><div class="gauge" role="meter" aria-label="Rating" aria-valuenow="4.6" aria-valuemin="1" aria-valuemax="5" style="--gauge-value: 4.6; --gauge-min: 1; --gauge-max: 5">  <span class="gauge-value">4.6</span>  <span class="gauge-label">of 5</span></div><div class="gauge" role="meter" aria-label="Queue" aria-valuenow="0" style="--gauge-value: 0">  <span class="gauge-value">0</span>  <span class="gauge-label">Queued</span></div>

#Sizes

A gauge is 120px across. gauge-sm is 88px, for a tile in a dense dashboard, and gauge-lg is 160px, for the one reading a view is about. The stroke is a fifteenth of the width and the figure scales with it. Without a gauge-label, the figure stands at the middle of the dial.

Sizes

<div class="gauge gauge-sm" role="meter" aria-label="Battery" aria-valuenow="64" style="--gauge-value: 64">  <span class="gauge-value">64%</span>  <span class="gauge-label">Battery</span></div><div class="gauge" role="meter" aria-label="Battery" aria-valuenow="64" style="--gauge-value: 64">  <span class="gauge-value">64%</span>  <span class="gauge-label">Battery</span></div><div class="gauge gauge-lg" role="meter" aria-label="Battery" aria-valuenow="64" style="--gauge-value: 64">  <span class="gauge-value">64%</span></div>

#A reading that is a status

The fill is ink. When the reading itself is a warning, set --gauge-color to a status color and say so in words: a hue never carries the meaning alone. The track is the same color stepped back, so it follows.

Status

<div class="gauge" role="meter" aria-label="Storage" aria-valuenow="84" aria-valuetext="84%, nearly full" style="--gauge-value: 84; --gauge-color: var(--color-warning)">  <span class="gauge-value">84%</span>  <span class="gauge-label">Nearly full</span></div><div class="gauge" role="meter" aria-label="Error budget" aria-valuenow="97" aria-valuetext="97% spent, over budget soon" style="--gauge-value: 97; --gauge-color: var(--color-danger)">  <span class="gauge-value">97%</span>  <span class="gauge-label">Spent</span></div>

#Gauge, meter or progress

A gauge and a meter both show a measurement within a range. Choose the gauge when the figure is the point and has room of its own, as on a dashboard; choose the meter for a line in a form or a row of a table. Progress is for a task that completes, and its ring closes; a gauge's arc never does.

#Accessibility

  • Give the element role="meter", an accessible name with aria-label or aria-labelledby, and the reading in aria-valuenow. Add aria-valuemin and aria-valuemax when the range is not 0 to 100.
  • Write the reading again as the text of gauge-value, with its unit. When the number needs words to be understood, add aria-valuetext.
  • The arc is drawn by the stylesheet and is not announced. In forced colors the fill takes the system highlight and the track the system's grey.

#Classes

ClassDescription
gaugeThe dial, 120px across, on an element with role="meter", an accessible name and the reading in aria-valuenow: two thirds of a ring, open at the foot, filled with ink as far as the reading
gauge-smAn 88px dial; the default is 120px. With gauge
gauge-lgA 160px dial; the default is 120px. With gauge
gauge-valueThe reading as text, in large tabular figures at the middle of the dial
gauge-labelOne quiet word under the figure, between the two ends of the arc; without it the figure stands at the middle

#Custom properties

PropertyDefaultDescription
--gauge-valueThe reading. It comes from aria-valuenow where typed attr() is supported; set it by hand elsewhere
--gauge-min0The foot of the range. It comes from aria-valuemin where typed attr() is supported
--gauge-max100The top of the range. It comes from aria-valuemax where typed attr() is supported
--gauge-size7.5remThe dial's diameter
--gauge-widthcalc(var(--gauge-size) / 15)Thickness of the arc
--gauge-colorvar(--color-surface-inverse)The ink of the fill; the track is the same color stepped back. Set a status color when the reading is one, and say so in words

#Source

@master/ui/components/gauge.css
/* * Gauge: a reading on a dial, for one measurement a reader takes in at a glance. * An element with role="meter" draws two thirds of a ring, open at the foot, and fills it with ink as far as * the reading has come. The figure stands in the middle and one word under it, between the two ends of the arc. * Where the browser supports typed attr(), the gauge reads aria-valuenow, aria-valuemin and aria-valuemax * itself, so the reading is written once, for people and for the drawing; elsewhere set --gauge-value. * The track and the fill both have round ends, and the fill eases to each new reading. * A gauge is 120px across (88 small, 160 large) and its stroke is a fifteenth of that. */@property --gauge-value {  syntax: '<number>';  inherits: true;  initial-value: 0;}@layer components {  .gauge {    --gauge-size: 7.5rem;    --gauge-width: calc(var(--gauge-size) / 15);    --gauge-color: var(--color-surface-inverse);    --gauge-min: 0;    --gauge-max: 100;    /* How far along the arc the reading is, from nothing to all of it. */    --gauge-share: clamp(0, calc((var(--gauge-value) - var(--gauge-min)) / (var(--gauge-max) - var(--gauge-min))), 1);    --gauge-radius: calc((var(--gauge-size) - var(--gauge-width)) / 2);    position: relative;    display: inline-grid;    flex: none;    align-content: end;    justify-items: center;    gap: calc(var(--gauge-size) * .05);    box-sizing: border-box;    width: var(--gauge-size);    /* The arc ends a quarter of the way below the middle, so the box stops there, and so does what it can scroll. */    height: calc(var(--gauge-size) * .75 + var(--gauge-width) / 4);    margin: 0;    overflow: clip;    color: var(--color-text-strong);    font: var(--font-weight-control) calc(var(--gauge-size) * .235)/1 var(--font-family-sans);    font-feature-settings: inherit;    font-variant-numeric: tabular-nums;    letter-spacing: -.02em;    text-align: center;    vertical-align: middle;    transition: --gauge-value var(--duration-base) var(--easing-glide);    @supports (x: attr(x type(*))) {      &[aria-valuenow] {        --gauge-value: attr(aria-valuenow type(<number>), 0);      }      &[aria-valuemin] {        --gauge-min: attr(aria-valuemin type(<number>), 0);      }      &[aria-valuemax] {        --gauge-max: attr(aria-valuemax type(<number>), 100);      }    }    /* Both drawings are a square as wide as the gauge, cut down to a ring; the box shows its upper three quarters. */    &::before, &::after {      content: '';      position: absolute;      inset: 0 0 auto;      height: var(--gauge-size);      border-radius: 999px;      mask: radial-gradient(farthest-side, transparent calc(100% - var(--gauge-width) - .5px), black calc(100% - var(--gauge-width)), black calc(100% - .5px), transparent);      pointer-events: none;    }    /*     * The track: the whole arc, from 240 to 480 degrees, with a dot at each end to round it. It is drawn in     * the fill's own color and stepped back as one layer, so the dots and the arc never add up.     */    &::before {      background:        radial-gradient(circle at calc(50% + var(--gauge-radius) * sin(240deg)) calc(50% - var(--gauge-radius) * cos(240deg)), var(--gauge-color) calc(var(--gauge-width) / 2 - .5px), transparent calc(var(--gauge-width) / 2)),        radial-gradient(circle at calc(50% + var(--gauge-radius) * sin(480deg)) calc(50% - var(--gauge-radius) * cos(480deg)), var(--gauge-color) calc(var(--gauge-width) / 2 - .5px), transparent calc(var(--gauge-width) / 2)),        conic-gradient(from 240deg, var(--gauge-color) 240deg, transparent 0);      opacity: .12;    }    /* The fill: the same arc as far as the reading. Its end dots have no radius at nothing, so an empty gauge is an empty track. */    &::after {      --gauge-end: calc(240deg + var(--gauge-share) * 240deg);      --gauge-cap: calc(var(--gauge-width) / 2 * min(1, var(--gauge-share) * 200));      background:        radial-gradient(circle at calc(50% + var(--gauge-radius) * sin(240deg)) calc(50% - var(--gauge-radius) * cos(240deg)), var(--gauge-color) calc(var(--gauge-cap) - .5px), transparent var(--gauge-cap)),        radial-gradient(circle at calc(50% + var(--gauge-radius) * sin(var(--gauge-end))) calc(50% - var(--gauge-radius) * cos(var(--gauge-end))), var(--gauge-color) calc(var(--gauge-cap) - .5px), transparent var(--gauge-cap)),        conic-gradient(from 240deg, var(--gauge-color) calc(var(--gauge-share) * 240deg), transparent 0);    }    /* Without a word under it, the figure stands at the middle of the dial. */    &:not(:has(.gauge-label)) {      align-content: center;      padding-top: calc(var(--gauge-size) * .2);    }    &.gauge-sm {      --gauge-size: 5.5rem;    }    &.gauge-lg {      --gauge-size: 10rem;    }    @media (prefers-reduced-motion: reduce) {      transition: none;    }    @media (forced-colors: active) {      --gauge-color: Highlight;      forced-color-adjust: none;      &::before {        --gauge-color: GrayText;        opacity: 1;      }    }  }  /* The reading, in the gauge's own large figures. */  .gauge-value {    position: relative;    z-index: 1;    max-width: 100%;  }  /* What is measured: one quiet word between the ends of the arc. */  .gauge-label {    position: relative;    z-index: 1;    max-width: 62%;    overflow: hidden;    color: var(--color-text-muted);    font: 400 .75rem/1rem var(--font-family-sans);    font-feature-settings: inherit;    letter-spacing: 0;    text-overflow: ellipsis;    white-space: nowrap;  }  .gauge-sm .gauge-label {    font-size: .6875rem;    line-height: .875rem;  }  .gauge-lg .gauge-label {    font-size: .8125rem;    line-height: 1.125rem;  }}