API
Meter
The native `<meter>`, restyled.
Installation
pnpm dlx zazz-ui add meternpx zazz-ui add meteryarn dlx zazz-ui add meterbunx zazz-ui add meterAlso adds progress, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.
Examples
<div
style="
--display: flex-col;
--gap: 4;
--w: 100%;
--max-w: 96;
"
>
<div data-ui="field">
<label
style="
--display: flex;
--items: center;
--justify: space-between;
"
data-field-slot="label"
for="f-storage"
>
<span>Storage used</span>
<span
style="
--font-size: var(--font-size-xs);
--text: var(--color-muted-foreground);
"
>52/100 GB</span
>
</label>
<meter
data-ui="meter"
id="f-storage"
min="0"
max="100"
low="60"
high="85"
optimum="30"
value="52"
>
52 GB of 100 GB
</meter>
</div>
<div data-ui="field">
<label
style="
--display: flex;
--items: center;
--justify: space-between;
"
data-field-slot="label"
for="f-storage-high"
>
<span>Nearly full</span>
<span
style="
--font-size: var(--font-size-xs);
--text: var(--color-muted-foreground);
"
>93/100 GB</span
>
</label>
<meter
data-ui="meter"
id="f-storage-high"
min="0"
max="100"
low="60"
high="85"
optimum="30"
value="93"
>
93 GB of 100 GB
</meter>
</div>
</div>/**
* meter.css: Meter ([data-ui~="meter"] on native <meter>)
*
* @layer variables, reset
* @requires _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved), progress.css
* @uses appearance: none: element box becomes the track; only the fill
* needs vendor pseudo-elements (::-webkit-meter-*-value,
* ::-moz-meter-bar + :-moz-meter-* state classes); never combine
* the two vendors in one selector list
* @uses attr(<attr> type(<number>)): where typed attr() exists
* (Chromium 133+) the fill is painted on the element from the
* meter's own attributes, because Blink locks the shadow parts'
* geometry (backgrounds apply, heights do not); @supports gated
* @uses Engines without typed attr() keep the vendor-pseudo fill;
* recorded degradation in Blink pre-133: themed track + thin
* centered fill (browser policy exception)
* @tokens --ui-meter-* (@layer variables); geometry aliases --ui-progress-*
*/
@layer variables {
:root {
/* Geometry rides the progress family so the two bars read identically;
retune a --ui-progress-* token and both follow. */
--ui-meter-w: var(--ui-progress-w);
--ui-meter-h: var(--ui-progress-h);
--ui-meter-rounded: var(--ui-progress-rounded);
--ui-meter-bg: var(--ui-progress-bg);
--ui-meter-track-border: var(--ui-progress-track-border);
/* Fills are semantic: a meter communicates good/bad, not completion */
--ui-meter-bar-bg: var(--color-success);
--ui-meter-bar-bg--suboptimum: var(--color-warning);
--ui-meter-bar-bg--even-less-good: var(--color-destructive);
}
}
@layer reset {
/* ===========================================================================
METER: native <meter> fill redraw
- Same track-on-the-element strategy as progress.css. WebKit exposes one
pseudo per state; Firefox exposes one fill pseudo colored by :-moz-meter-*
state classes. One rule per vendor pseudo, by necessity.
=========================================================================== */
:where([data-ui~="meter"]) {
--_w-len: var(--_w-resolved, var(--ui-meter-w));
--_w-num: var(--_w-resolved, var(--ui-meter-w));
inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
--_h-len: var(--_h-resolved, var(--ui-meter-h));
--_h-num: var(--_h-resolved, var(--ui-meter-h));
block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
overflow: hidden;
background-color: var(--_bg-resolved, var(--ui-meter-bg));
border: var(--ui-meter-track-border);
border-radius: var(--_rounded-resolved, var(--ui-meter-rounded));
}
:where([data-ui~="meter"])::-webkit-meter-bar {
background: transparent;
border: none;
block-size: 100%;
}
/* The value pseudos keep a UA-default height unless given one explicitly */
:where([data-ui~="meter"])::-webkit-meter-optimum-value {
block-size: 100%;
background: var(--ui-meter-bar-bg);
border-radius: inherit;
}
:where([data-ui~="meter"])::-webkit-meter-suboptimum-value {
block-size: 100%;
background: var(--ui-meter-bar-bg--suboptimum);
border-radius: inherit;
}
:where([data-ui~="meter"])::-webkit-meter-even-less-good-value {
block-size: 100%;
background: var(--ui-meter-bar-bg--even-less-good);
border-radius: inherit;
}
:where([data-ui~="meter"])::-moz-meter-bar {
block-size: 100%;
border-radius: inherit;
}
:where([data-ui~="meter"]:-moz-meter-optimum)::-moz-meter-bar {
background: var(--ui-meter-bar-bg);
}
:where([data-ui~="meter"]:-moz-meter-sub-optimum)::-moz-meter-bar {
background: var(--ui-meter-bar-bg--suboptimum);
}
:where([data-ui~="meter"]:-moz-meter-sub-sub-optimum)::-moz-meter-bar {
background: var(--ui-meter-bar-bg--even-less-good);
}
/* ===========================================================================
ATTRIBUTE-PAINTED FILL: Blink accepts backgrounds on the meter shadow parts
but ignores their geometry entirely (author heights, even !important, never
apply), leaving a thin centered fill. Where typed attr() exists, paint the
fill on the element itself instead: a hard-stop gradient sized from the
meter's own attributes, reproducing the HTML spec's region algorithm:
value and optimum each land in a low/between/high region, and the distance
between the two regions (0/1/2) picks optimal / suboptimal / even-less-good.
The shadow fills are blanked underneath so nothing double-paints.
=========================================================================== */
@supports (inline-size: calc(attr(value type(<number>), 0) * 1px)) {
:where([data-ui~="meter"]) {
/* Attribute facts, with the spec's defaults */
--_val: attr(value type(<number>), 0);
--_min: attr(min type(<number>), 0);
--_max: attr(max type(<number>), 1);
--_low: attr(low type(<number>), var(--_min));
--_high: attr(high type(<number>), var(--_max));
--_opt: attr(optimum type(<number>), calc((var(--_min) + var(--_max)) / 2));
--_pct: calc((var(--_val) - var(--_min)) / (var(--_max) - var(--_min)) * 100%);
/* Region index 0/1/2 = below low / between / above high. The ×100000
saturation stands in for sign(); the +1 makes the low bound inclusive.
Assumes attribute deltas of at least 0.00001. */
--_region-val: calc(
clamp(0, (var(--_val) - var(--_low)) * 100000 + 1, 1) +
clamp(0, (var(--_val) - var(--_high)) * 100000, 1)
);
--_region-opt: calc(
clamp(0, (var(--_opt) - var(--_low)) * 100000 + 1, 1) +
clamp(0, (var(--_opt) - var(--_high)) * 100000, 1)
);
--_distance: max(
var(--_region-val) - var(--_region-opt),
var(--_region-opt) - var(--_region-val)
);
/* distance 0 → optimal, 1 → suboptimal, 2 → even less good */
--_fill: color-mix(
in oklch,
var(--ui-meter-bar-bg--even-less-good) calc(clamp(0, var(--_distance) - 1, 1) * 100%),
color-mix(
in oklch,
var(--ui-meter-bar-bg--suboptimum) calc(min(var(--_distance), 1) * 100%),
var(--ui-meter-bar-bg)
)
);
background-image: linear-gradient(
to right,
var(--_fill) var(--_pct),
transparent var(--_pct)
);
background-origin: padding-box;
background-clip: padding-box;
}
:where([data-ui~="meter"]:dir(rtl)) {
background-image: linear-gradient(to left, var(--_fill) var(--_pct), transparent var(--_pct));
}
/* The shadow fills would double-paint under the gradient (the UA styles
these parts with a bevel background-image) */
:where([data-ui~="meter"])::-webkit-meter-optimum-value {
background: transparent;
}
:where([data-ui~="meter"])::-webkit-meter-suboptimum-value {
background: transparent;
}
:where([data-ui~="meter"])::-webkit-meter-even-less-good-value {
background: transparent;
}
:where([data-ui~="meter"])::-moz-meter-bar {
background: transparent;
}
}
}Attributes
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="meter" | Identity | free value | Identity token in data-ui. |
Requires: progress.
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-meter-bar-bg | var(--color-success) | — |
--ui-meter-bar-bg--even-less-good | var(--color-destructive) | even-less-good |
--ui-meter-bar-bg--suboptimum | var(--color-warning) | suboptimum |
--ui-meter-bg | var(--ui-progress-bg) | — |
--ui-meter-h | var(--ui-progress-h) | — |
--ui-meter-rounded | var(--ui-progress-rounded) | — |
--ui-meter-track-border | var(--ui-progress-track-border) | — |
--ui-meter-w | var(--ui-progress-w) | — |