Accessibility pattern · Feedback and status

Meter

A meter shows where a measurement sits in a known range: storage used, password strength, battery left. It is not a progress bar, which tracks a task moving towards done. The native element colors itself from its low, high and optimum values; the same state is written beside it in words, and aria-valuetext gives the number its unit.

WCAG criteria
6
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Backup

Your phone backs up every night on Wi-Fi.

7.2 GB of 10 GB

Almost full

Fair

Fair

Phone battery

Good

Try other values

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabSkips the meters, which are read-only, and moves between the three sliders that set their values.
Arrow Right or Arrow UpOn a slider, raises the value one step; the meter, its value text and its state word follow.
Arrow Left or Arrow DownOn a slider, lowers the value one step.
Home or EndOn a slider, sets the lowest or highest value.
Page Up or Page DownOn a slider, moves by a bigger step.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
A screen reader reaches the storage meterCloud storage, meter, 7.2 GB of 10 GB. Almost full
It reaches the battery, drawn with role=meterPhone battery, meter, 64%. Good
Focus reaches a sliderSet storage used, slider, 7.2 GB
A slider takes storage below the high markCloud storage: Filling up
The password slider reaches the topPassword strength: Strong

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<section class="ap-meter" data-ap-meter aria-labelledby="meter-name">
  <div class="ap-meter__top">
    <h3 class="ap-meter__name" id="meter-name">Backup</h3>
    <p class="ap-meter__sub">Your phone backs up every night on Wi-Fi.</p>
  </div>

  <div class="ap-meter__row" data-ap-row="storage" data-state="poor">
    <div class="ap-meter__line">
      <label class="ap-meter__label" for="meter-storage">Cloud storage</label>
      <span class="ap-meter__value" aria-hidden="true"><span data-ap-shown>7.2 GB</span> <span class="ap-meter__of">of 10 GB</span></span>
    </div>
    <meter class="ap-meter__bar" id="meter-storage" min="0" max="10" low="5" high="7" optimum="0" value="7.2" aria-valuetext="7.2 GB of 10 GB" aria-describedby="meter-storage-state">7.2 GB of 10 GB</meter>
    <p class="ap-meter__state" id="meter-storage-state">
        <svg class="ap-meter__icon ap-meter__icon--good" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="m8 12.5 2.8 2.8L16.2 9.6"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--fair" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--poor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M10.3 4.1 2.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.1a2 2 0 0 0-3.4 0Z"/><path d="M12 9.5v4"/><path d="M12 17h.01"/></svg>
      <span data-ap-word>Almost full</span>
    </p>
  </div>

  <div class="ap-meter__row" data-ap-row="password" data-state="fair">
    <div class="ap-meter__line">
      <label class="ap-meter__label" for="meter-password">Password strength</label>
      <span class="ap-meter__value" aria-hidden="true"><span data-ap-shown>2</span> <span class="ap-meter__of">of 4</span></span>
    </div>
    <meter class="ap-meter__bar" id="meter-password" min="0" max="4" low="1.5" high="2.5" optimum="4" value="2" aria-valuetext="Fair">Fair</meter>
    <p class="ap-meter__state">
        <svg class="ap-meter__icon ap-meter__icon--good" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="m8 12.5 2.8 2.8L16.2 9.6"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--fair" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--poor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M10.3 4.1 2.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.1a2 2 0 0 0-3.4 0Z"/><path d="M12 9.5v4"/><path d="M12 17h.01"/></svg>
      <span data-ap-word>Fair</span>
    </p>
  </div>

  <div class="ap-meter__row" data-ap-row="battery" data-state="good">
    <div class="ap-meter__line">
      <span class="ap-meter__label" id="meter-battery-label">Phone battery</span>
      <span class="ap-meter__value" aria-hidden="true"><span data-ap-shown>64%</span></span>
    </div>
    <div class="ap-meter__battery" role="meter" aria-labelledby="meter-battery-label" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64" aria-valuetext="64%" aria-describedby="meter-battery-state" data-low="20" data-high="40" data-optimum="100" data-state="good">
      <span class="ap-meter__cells"><span class="ap-meter__fill"></span></span>
      <span class="ap-meter__nub"></span>
    </div>
    <p class="ap-meter__state" id="meter-battery-state">
        <svg class="ap-meter__icon ap-meter__icon--good" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="m8 12.5 2.8 2.8L16.2 9.6"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--fair" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>
        <svg class="ap-meter__icon ap-meter__icon--poor" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M10.3 4.1 2.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.1a2 2 0 0 0-3.4 0Z"/><path d="M12 9.5v4"/><path d="M12 17h.01"/></svg>
      <span data-ap-word>Good</span>
    </p>
  </div>

  <fieldset class="ap-meter__try">
    <legend class="ap-meter__legend">Try other values</legend>
    <div class="ap-meter__slider">
      <label for="meter-try-storage">Set storage used</label>
      <input type="range" id="meter-try-storage" min="0" max="10" step="0.1" value="7.2" aria-valuetext="7.2 GB" data-ap-drive="storage" />
    </div>
    <div class="ap-meter__slider">
      <label for="meter-try-password">Set password strength</label>
      <input type="range" id="meter-try-password" min="0" max="4" step="1" value="2" aria-valuetext="Fair" data-ap-drive="password" />
    </div>
    <div class="ap-meter__slider">
      <label for="meter-try-battery">Set battery level</label>
      <input type="range" id="meter-try-battery" min="0" max="100" step="1" value="64" aria-valuetext="64%" data-ap-drive="battery" />
    </div>
  </fieldset>

  <p class="ap-meter__vh" role="status" data-ap-live></p>
</section>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.4.3 Contrast (Minimum) Level AA

    The state words use the success, warning and danger text colors, which clear 4.5:1 on the card in light and dark.

  • 1.3.1 Info and Relationships Level A

    The native meter and the role=meter battery expose their minimum, maximum and current value, and each is named by its visible label.

  • 1.4.1 Use of Color Level A

    Green, amber and red are never the only signal: every row says Almost full, Fair or Good in words, with a different icon for each state.

  • 1.4.11 Non-text Contrast Level AA

    The fill clears 3:1 against the track and the card in both themes, and the battery outline uses the control-edge token.

  • 4.1.2 Name, Role, Value Level A

    role=meter with aria-valuenow, aria-valuemin and aria-valuemax gives the drawn battery the same name, role and value as the native element.

  • 4.1.3 Status Messages Level AA

    When a value crosses into a new state, a polite status message says so once, without moving focus or repeating every step.

Usage

When to use it

Use it

  • A measurement within a known range: disk or plan usage, battery, signal, a password's strength, a budget spent.
  • Values where ranges matter, so low, high and optimum can say whether the reading is good or bad.

Use something else

  • How far a task has got, like an upload or an install: that is a progress bar.
  • A value people set themselves: that is a slider.
  • A number with no meaningful maximum, like followers or page views: show the number.

Common failures

How it usually goes wrong

  • A progress bar used as a gauge

    role=progressbar tells people something is loading and will finish. Storage at 72% is not on its way to 100; it is a reading, so it is a meter.

  • The state shown only by color

    A bar that turns red is invisible as a warning to many people with color blindness and to anyone in high-contrast mode. The word Almost full says it for everyone.

  • A bare number with no unit

    A meter with value 7.2 is announced as "7.2", or as a percentage the browser works out. aria-valuetext="7.2 GB of 10 GB" says what the number means.

  • A drawn bar with no role

    A div with a colored width is just decoration to assistive technology. role=meter and its value attributes, or the native element, make it a reading.

  • Every change announced

    A live region that reads out each new value while a slider moves floods the speech queue. Announcing only the change of state keeps it useful.

Notes

Building it

  • Set low, high and optimum and the browser picks the bar's color: optimum below low means lower is better (storage), above high means higher is better (battery). The script works the state out the same way, so the word and the color always agree.
  • aria-valuetext replaces the number for screen readers, so put the unit in it and keep it short. Link the state word with aria-describedby, unless the value text already is that word, as with the password.
  • Use role=meter only when the native element cannot be drawn the way you need, like the battery here, and give it aria-valuenow, aria-valuemin, aria-valuemax and a name.
  • Meters are read-only and take no tab stop. The sliders under them are demo controls for trying values; in a real product the value comes from your data.
  • Style the native bar with ::-webkit-meter-* and ::-moz-meter-bar in separate rules: one unknown pseudo-element in a selector list drops the whole rule.

Sources: HTML: the meter element · WAI-ARIA Authoring Practices: Meter

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction