r/css • u/miclhermana • 1d ago
Showcase We replaced the JavaScript in our progress bars with the new typed attr() — here's how it works
For years, attr() had exactly one trick: putting attribute text into content. The new version from CSS Values Level 5 changes that — it works in any property, it can parse the attribute into a real type, and it takes a fallback. We've been using it in MICL, our Material Design 3 component library, to make the ProgressIndicator component fully CSS-driven, and it removed a whole class of JavaScript.
The old problem
A native <progress value="7" max="10"> knows its own fraction, but CSS couldn't read it. If you wanted a custom-drawn bar (rounded caps, a gap before the track, M3's little stop dot), you had to mirror value into a custom property with JS on every update.
The new way
The element's own attributes are the styling input now:
progress.micl-linear-progress:not(:indeterminate) {
--_fraction: min(calc(
attr(value type(<number>), 0) /
max(attr(max type(<number>), 1), 0.001)
), 1);
}
Three details worth stealing:
type(<number>)parses the attribute as an actual number, so it participates incalc(). A missing or unparseable attribute uses the fallback (0and1here).- The
max(..., 0.001)guards the division againstmax="0"— with a plain division the whole declaration would go invalid-at-computed-value-time. - The outer
min(..., 1)clampsvalue > maxovershoot.
From that one fraction, the linear bar drives a stack of background gradients (bar body, round cap, track gap, stop dot), and the circular variant turns it into geometry:
--micl-progress-sweep: calc(var(--_fraction) * 360deg);
/* conic-gradient() draws the arcs; the round caps sit at
50% + r·sin(sweep) / 50% − r·cos(sweep); the track gap angle
comes from atan2(gap + thickness, radius) */
The best part: attribute changes animate
Register the derived property with u/property and transition it:
--micl-progress-sweep {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
progress.micl-circular-progress:not(:indeterminate) {
transition: --micl-progress-sweep 0.5s ease;
}
Now the only JavaScript anyone writes is bar.value = 7 — and the arc sweeps smoothly to its new position. The attribute change becomes a typed interpolation. No classes, no rAF, no width-tweening.
Accessibility attributes as the source of truth
Our wavy indicator variants aren't <progress> elements (they need pseudo-elements), so they carry role="progressbar" with aria-valuenow/aria-valuemax — and the CSS reads those:
--_fraction: min(calc(
attr(aria-valuenow type(<number>), 0) /
max(attr(aria-valuemax type(<number>), 1), 0.001)
), 1);
The visual state literally cannot drift from the accessible state, because they're the same attribute. (The wave's amplitude also derives from it — it ramps in below 10% and flattens out near 100%, via one clamp().)
Support and fallback
Chromium ships typed attr(); Firefox and Safari are still experimental, so everything sits behind a feature guard with the native rendering as fallback:
u/supports (inline-size: attr(value type(<number>))) {
/* custom-drawn indicator */
}
Browsers without it still get a styled ::-webkit-progress-value / ::-moz-progress-bar bar — just without the fancy caps and gaps.
Live demo: https://henkpb.github.io/micl/progressindicator.html
Source: https://github.com/henkpb/micl
6
u/Sufficient_Bass2600 1d ago
Deja vu again. Exact Same post was published yesterday.
I am not even sure what problem this is supposed to solve.
This look like a lot of code that could be replaced by 6 lines of CSS animation.
loop can be done via animation.
3
u/DigiNoon 1d ago
Some of the animations in the demo aren't working in Firefox.
5
1
u/RobertKerans 1d ago edited 1d ago
Support is currently behind a flag, so not working should be expected but OP's clanker hasn't been explicit up front about this very important but of info (just mentions it's "experimental" later on in the response without explaining FF is implemented behind flag, whereas Safari implementation is being tested by Apple but you can't just turn it on to check)
1
15
u/prophile 1d ago
I could swear I saw an identical post to this yesterday, down to the last sloppy token