CONTRIBUTOR-DOCS / Project planning / Components / Meter / Meter accessibility migration analysis
In this doc
This doc explains how swc-meter should work for accessibility. It supports WCAG 2.2 Level AA. Until swc-meter exists under 2nd-gen/, use 1st-gen/packages/meter/src/Meter.ts (<sp-meter>) to validate behavior, and update this spec against the real 2nd-gen source when it ships.
- A read-only bar (track + fill) that shows a value within a fixed range (today modeled as 0–100 on
progress) to represent a level or amount (for example storage use or another bounded measurement). - It is not a control the user adjusts with the keyboard; it is for display.
- Task completion, file upload, or indeterminate “busy until done” ➜ use
progressbarand Progress bar accessibility migration analysis (or Progress circle for a ring). Those patterns match Adobe’s Figma Loading animation discovery and the APG progress bar pattern; a meter is not a substitute for a loader or indeterminate progress. - A value the user sets (slider) ➜ use a slider-style component, not a meter.
- A graph of population with no real maximum, or a trend without a fixed min/max (the APG About this pattern note gives examples of what a meter is not for).
- Bar-shaped task progress ➜ Progress bar (same bar visuals in designs; different ARIA role and product meaning).
- Circular task progress ➜ Progress circle.
- The WAI-ARIA meter pattern is the right fit: the element with
role="meter"exposesaria-valuemin,aria-valuemax,aria-valuenow, a label viaaria-labelledby,aria-label, and optionallyaria-valuetext, when a percentage alone would be unclear. Keyboard interaction is not applicable for a read-only meter. - Do not use a meter for “progress” of a long-running process in the “loading / unknown time” sense; the APG and Using ARIA distinguish that from a
progressbar.
| Idea | Plain meaning |
|---|---|
meter role |
The node needs an accessible name and a value between min and max. Use aria-valuetext when a raw number or percent would not be enough (for example a battery string like the APG example: “50% (6 hours) remaining”). If the only meaningful description is a localized percent, aria-valuetext can align with the visible percentage. |
| Name, role, value (WCAG 4.1.2) | Name from label, slot text, aria-label, or aria-labelledby. Role must be meter, not progressbar, for this component. Value = min, max, now, and (when used) valuetext. |
| Non-text content (WCAG 1.1.1) | Track and fill are in shadow DOM; the host should expose role and ARIA values. Inner nodes stay graphical unless a separate spec says otherwise. |
| Use of color (WCAG 1.4.1) | Do not rely on fill color or variant alone. Value, name, and visible percentage should agree. Variant (positive / notice / negative) is a decorative/semantic tint for humans only unless mapped with care in docs and tests. |
| Non-text contrast (WCAG 1.4.11) | Track and fill are graphical and need at least 3:1 with adjacent colors. At 0% or with very low fill, a thin or faint track alone can fail, similar to a progress bar. |
| Loading visuals (Figma) | The Loading animation discovery Figma covers loaders and task progress and also prescribes bar / track / fill treatments that help contrast at 0% (or very low fill). Use that file as one way to mitigate non-text contrast for the bar at value = 0 while keeping ARIA and the stated value as a meter (not a loader). Meters show a static level in a fixed range—not a spinner or indeterminate “unknown length” task. Keep bar + side label treatment consistent when a meter and a progress bar appear in one UI, but do not add indeterminate loading motion to a meter. If motion is ever added, apply WCAG 2.2.2 and reduced-motion rules. |
Bottom line: Ship a non-focusable role="meter" with a name, min (0), max (100), now (from progress), and valuetext that matches the localized percent the component shows, as implemented in 1st-gen/.../Meter.ts today for display—then verify in swc-meter.
| Jira | Type | Status (snapshot) | Resolution (snapshot) | Summary |
|---|---|---|---|---|
| — | — | — | — | No 1st-gen sp-meter–specific items in this snapshot. |
| Topic | What to do |
|---|---|
role="meter" |
Prescribed and fixed on the host. It must not be author-overridable. Do not set role="progressbar" and do not use a combined or space-separated role string (1st-gen’s role="meter progressbar" is not a valid use of a single ARIA role value). This element is one meter only. If authors need a task progress widget, they should use <swc-progress-bar> / <swc-progress-circle>, not a role change on the meter. |
| Name (required) | Provide a name with label, default slot text, aria-label, or aria-labelledby (WCAG 4.1.2). Prefer aria-labelledby when a visible label exists; otherwise use aria-label. A dev warning in debug builds (pattern from progress components) is appropriate when no name is provable. |
aria-valuemin / aria-valuemax |
Set to "0" and "100" to match the current public progress API (0–100). 1st-gen does not set these; 2nd-gen should. If the product later allows arbitrary ranges, recompute all three of min, max, and now from the same source of truth as the visible value. |
aria-valuenow |
Mirror progress, updated on change (1st-gen does this). |
aria-valuetext |
Expose a string that matches the displayed percentage (for example the same localized value as the sp-field-label in the percentage area). This keeps AT in sync with sighted users, and satisfies the APG when a percent is a sensible “human” value. If the value should not be spoken as a percent only, set aria-valuetext to the user-friendly string. |
label / slot |
If slot text drives label, document one clear naming path. Avoid a name that only repeats “Meter” or a size when you can be specific (for example “Storage”). |
| 0% appearance (WCAG 1.4.11) | When progress = 0, the fill is effectively invisible—at-risk non-text contrast on the track only. Mitigate using the bar / track / fill rules in the Loading animation discovery Figma (and align with the Progress bar 0% guidance where the bar is shared), so graphical parts meet 3:1; aria-valuenow, aria-valuetext, and any visible percent must still read as 0%—treatment is for perception only. |
variant / size / static-color / side-label |
Layout and tint only, unless a future spec ties variant to advisory (non-essential) hints—do not invent ARIA mappings without product agreement. |
| Docs | Read-only: no Tab focus for the default use case; not a loader. Warn against over-announcing: do not use aria-live="assertive"; use aria-live="polite" only in rare cases when frequent updates are described elsewhere in one primary place. Loaders and indeterminate work belong on progressbar, aligned with the Figma Loading animation discovery doc and the progress components, not on a meter. |
Keep role="meter" and the value-related ARIA on the custom element host. Authors can then use aria-labelledby and aria-describedby on <swc-meter> / <sp-meter> with id references to elements in the surrounding light DOM; those references live in the same document tree as the host and do not need to cross a shadow boundary for the usual label / description pattern.
If the meter role were placed only on a node inside closed shadow DOM instead, the custom element would not be able to rely on the same aria-labelledby and aria-describedby pattern: IDs in the light DOM do not resolve as cross-reference targets for inner shadow nodes, and ARIA on the host would not name a meter that lives only in the shadow tree. You would need a different approach (for example ElementInternals and delegation, or another markup strategy). This component should keep meter semantics on the host so that pattern stays available.
- Role: meter with a name and valuemin, valuemax, valuenow, and valuetext (when 2nd-gen adds min/max/valuetext) aligning with the visible percentage.
- The side or top labels in shadow should not cause redundant naming if the host also carries a full accessible name—implementation should be checked so assistive technology does not read the same title twice; prefer one clear naming strategy in code.
- Tree name and value stay independent of variant; color is not a substitute for name and value.
Not focusable. Keyboard navigation should skip this component and move to the next focusable element.
Gaps in 1st-gen <sp-meter> that 2nd-gen swc-meter should fix and cover with tests.
- The host uses
setAttribute('role', 'meter progressbar'). ARIArolemust be a single token from the spec; the correct fixed role for this widget ismeter, notprogressbar, per the APG meter pattern. aria-valuemin,aria-valuemax, andaria-valuetextare not set inMeter.ts(onlyaria-valuenow,aria-label, and therolestring are set in code paths reviewed here). They should be added in 2nd-gen to match the pattern.
- At
progress= 0, the fill has no width, similar to a progress bar—at-risk 3:1 for track vs background. One practical way to mitigate is to follow the bar / track / fill treatment prescribed in the Loading animation discovery Figma (and the Progress bar recommendations for a shared 0% story with contrast checks).
- 1st-gen
1st-gen/packages/meter/README.mddescribesrole="meter progressbar"as if both roles apply. Update consumer docs forswc-meterto a singlemeterrole and a short “meter vs progress” blurb (task progress vs static level).
| Kind of test | What to check |
|---|---|
| Unit | Single role="meter" (or equivalent internal mapping). Min/max/now/valuetext and name (slot / label / aria-label) in or with swc-meter. Host not focusable by default. |
| aXe + Storybook | WCAG 2.x on meter stories (1st-gen now; 2nd-gen when added). |
| Playwright ARIA snapshots | Add meter.a11y.spec.ts for 2nd-gen, covering side-label, sizes, variants, and key progress values, mirroring the progress-bar / progress-circle snapshot approach. |
| Contrast | 0%, variants, staticColor, and any over-photo or on-image use cases. |
The meter is not in the Tab order, so you will not reach it the same way as focusable controls when a screen reader is in forms or application-style focus navigation. Use browse mode (document or scan mode) to read the page in content order and encounter the meter so you can verify its name and value are announced. See the 2nd-gen Storybook Screen reader testing guide, including Browse mode (document/scan mode).
- 2nd-gen exposes
role="meter"only, with valuemin, valuemax, valuenow, and valuetext aligned toprogress, label, and locale (verify inswc-metersource). - Docs and stories distinguish meter (static level in a range) from progress (task and, when used, indeterminate). Cite Loading animation discovery for shared bar / 0% treatments (contrast) and for loader / task progress—not for indeterminate loading motion on a meter.
- No
aria-live="assertive";politelive regions are rare and only when a single primary announcement strategy exists. - 0% and variant stories meet non-text contrast expectations, including 0% mitigations aligned with the Figma Loading file where the bar spec applies.
- Dev warning (if any) uses the
swc-metername and a useful issues list. - 1st-gen issues (combined role, missing min/max/valuetext) are regression-tested in 2nd-gen.
- aXe (WCAG 2.x) runs on meter stories.
- Manual screen reader testing uses browse mode per the Storybook Screen reader testing guide, because the meter is not keyboard focusable.
- WAI-ARIA 1.2:
meterrole - APG: meter pattern
- WAI-ARIA 1.2:
progressbar(for “not a meter”) - Using ARIA (read this first)
- WCAG 2.2
- WCAG 1.4.11: non-text contrast (understanding)
- WCAG 2.2.2: pause, stop, hide (understanding)
- Figma: Loading animation discovery
- Progress bar accessibility migration analysis
- Progress circle accessibility migration analysis
- Meter migration roadmap
- 2nd-gen Storybook: Screen reader testing