Meter

Summary

A labeled value with a small track showing consumption against a bound: budget spend, quota, capacity. It reads as a level, not an activity: the substrate for the Cortex cost-meter affordance (see the Nware Cortex kit). Threshold coloring is the consumer's call. Add -warning / -danger when crossing 75% / 90%, or whatever the domain says.

When to use

FormUse it for
.soma-meterA fraction of a budget, quota or capacity, in a widget or drawer: storage used, monthly spend, seat count.
.soma-meter-inlineA persistent affordance in the navbar or a toolbar, such as the always-visible cost meter.
-warning / -dangerCrossing domain thresholds. The consumer applies the class; the meter just paints it.
.soma-progress insteadTask completion, something running toward done. Meter = fraction of a budget; progress = fraction of a task.

Examples

Default

Primary bar below thresholds:

Cost contribution $245 of $500 cap

Header label & value, 4px track underneath. The nested <small> mutes the bound.

Warning threshold

Consumer-applied -warning:

Budget $2,450 of $3,000

The bar turns warning; the threshold (here 75%) is domain logic, not the meter's.

Danger threshold

Consumer-applied -danger:

API quota 9,612 of 10,000 calls

-danger colors the bar and tints the value text.

Without a header value

Label + track only:

Disk usage

The visible value span is optional. When you drop it, keep the numbers in the aria-label so the meter still reads fully to AT.

Inline — the cost-meter affordance

soma-meter-inline in a navbar strip, next to other persistent controls:

$42.10/d $2,450 of $3,000

The inline form flattens header and track onto one row with a fixed 64px track: label · value · track. Combines with the threshold classes as usual.

Live thresholds

Consumer JS applies -warning at 75%, -danger at 90%:

Budget $1,200 of $3,000

Each click updates bar width, aria-valuenow, the visible value and the threshold class in one pass. The wiring is the snippet in the JavaScript section.

HTML

The full form puts the header (label + value) over the track. The ARIA contract: role="meter" with aria-valuenow / aria-valuemin / aria-valuemax and an accessible name:

<div class="soma-meter" role="meter" aria-valuenow="82"
     aria-valuemin="0" aria-valuemax="100" aria-label="Monthly budget">
  <div class="soma-meter-header">
    <span class="soma-meter-label">Budget</span>
    <span class="soma-meter-value">$2,450 <small>of $3,000</small></span>
  </div>
  <div class="soma-meter-track"><div class="soma-meter-bar" style="width: 82%"></div></div>
</div>

Thresholds are extra classes on the root — the markup is otherwise identical:

<div class="soma-meter soma-meter-warning" role="meter" …>   <!-- crossed 75% -->
<div class="soma-meter soma-meter-danger"  role="meter" …>   <!-- crossed 90% -->

For label-only, drop the value span but keep the numbers in the aria-label so the meter still reads fully to AT:

<div class="soma-meter" role="meter" aria-valuenow="35"
     aria-valuemin="0" aria-valuemax="100" aria-label="Disk usage 35% of 80 GB">
  <div class="soma-meter-header">
    <span class="soma-meter-label">Disk usage</span>
  </div>
  <div class="soma-meter-track"><div class="soma-meter-bar" style="width: 35%"></div></div>
</div>

The inline (cost-meter) form — same children, one extra class; header and track flatten onto a single row with a fixed 64px track:

<div class="soma-meter soma-meter-inline soma-meter-warning" role="meter"
     aria-valuenow="82" aria-valuemin="0" aria-valuemax="100"
     aria-label="Monthly budget 82% used">
  <div class="soma-meter-header">
    <span class="soma-meter-label">$42.10/d</span>
    <span class="soma-meter-value">$2,450 <small>of $3,000</small></span>
  </div>
  <div class="soma-meter-track"><div class="soma-meter-bar" style="width: 82%"></div></div>
</div>

When the value changes, update the bar width, aria-valuenow and the visible value together. See the JavaScript section for the one function that does all three plus the threshold class.

CSS classes

ClassEffect
.soma-meterThe root (min-width 120px). Carries the role="meter" ARIA contract above.
.soma-meter-headerBaseline-aligned row: label at the start, value at the end.
.soma-meter-labelSmall uppercase label.
.soma-meter-valueThe current value; a nested <small> mutes the bound ("of $3,000"). Optional. Keep the numbers in aria-label when omitted. Rendered in the mono stack with tabular figures so a ticking readout doesn't shift the header.
.soma-meter-track / .soma-meter-bar4px track and its fill. Set the bar's width inline. Width changes animate over 250ms (no transition under reduced motion).
.soma-meter-warning / .soma-meter-dangerThreshold coloring on the root; -danger also tints the value text (via --soma-color-danger-subtle-fg, an AA text color in every theme).
.soma-meter-inlineCompact horizontal form for the navbar: label · value · fixed 64px track on one row. Combines with the threshold classes.

JavaScript

None as a component API — the meter is pure CSS. The consumer owns the update: bar width, aria-valuenow, the visible value and the threshold class all describe the same number, so change them in one function or they drift. The thresholds themselves are domain logic — 75%/90% here, but a seat count might flip straight to danger at 100%:

function updateMeter(el, spent, cap, format) {
  const fraction = Math.max(0, Math.min(1, spent / cap));
  const percent = Math.round(fraction * 100);

  el.querySelector('.soma-meter-bar').style.width = `${percent}%`;
  el.setAttribute('aria-valuenow', String(percent));
  el.setAttribute('aria-label', `Monthly budget ${percent}% used`);
  el.querySelector('.soma-meter-value').innerHTML =
    `${format(spent)} <small>of ${format(cap)}</small>`;

  // Domain thresholds — the meter just paints whatever class it's given.
  el.classList.toggle('soma-meter-warning', fraction >= 0.75 && fraction < 0.9);
  el.classList.toggle('soma-meter-danger', fraction >= 0.9);
}

updateMeter(document.querySelector('#budget'), 2450, 3000,
  (n) => '$' + n.toLocaleString());

Polling a live source: same function, on an interval; the bar animates between values on its own:

setInterval(async () => {
  const { spent, cap } = await fetch('/api/budget').then((r) => r.json());
  updateMeter(document.querySelector('#budget'), spent, cap, formatMoney);
}, 60_000);

The cost-meter affordance

The Cortex spec's CostMeter is this component — no wrapper, no extra API. The composition: a -inline meter as a persistent navbar affordance (burn rate as the label, spend against cap as the value), next to the kill switch and the notifications bell, exactly as the inline example above shows. Crossing the domain's warning threshold flips -warning on; a page-level banner (not the meter) carries any "budget nearly exhausted" interruption. See the Nware Cortex kit for the full trust-primitive mapping.

The live-thresholds example above, exactly as this page wires it (the same updateMeter shape, thresholds at 75%/90%):

const meter = document.getElementById('meter-live');
const CAP = 3000;
let spent = 1200;

function render() {
  const fraction = Math.min(1, spent / CAP);
  const percent = Math.round(fraction * 100);
  meter.querySelector('.soma-meter-bar').style.width = `${percent}%`;
  meter.setAttribute('aria-valuenow', String(percent));
  meter.setAttribute('aria-label',
    `Monthly budget $${spent.toLocaleString()} of $${CAP.toLocaleString()}`);
  meter.querySelector('.soma-meter-value').innerHTML =
    `$${spent.toLocaleString()} <small>of $${CAP.toLocaleString()}</small>`;
  meter.classList.toggle('soma-meter-warning', fraction >= 0.75 && fraction < 0.9);
  meter.classList.toggle('soma-meter-danger', fraction >= 0.9);
}

document.getElementById('meter-spend').addEventListener('click', () => {
  spent = Math.min(CAP, spent + 450);
  render();
});
document.getElementById('meter-reset').addEventListener('click', () => {
  spent = 1200;
  render();
});