<progress></progress>
2024-11-12
The ridiculously confusing task of styling a decent-looking progress bar
You would think it's easy. I mean, it's just one teeny tiny, simple progress element!
Let's try to do something basic
Can we change the height, width and accent-color?
progress {
accent-color: tomato;
height: 2rem;
width: 100%;
}
It works! I don't like that border-radius though. Let's set it to 0!
progress {
accent-color: tomato;
border-radius: 0;
height: 2rem;
width: 100%;
}
Uh-oh. It just... disappeared? Uhm.. ok let's ignore that then.
It became gray, that's weird. How about doing something about that background color instead then?
progress {
accent-color: tomato;
background-color: yellow;
height: 2rem;
width: 100%;
}
Gray, still?! What did I even do? 😱
Pseudo-element heaven
Turns out the <progress>
element is the forgotten middle child in the browser interop program. Pseudo-elements and classes you'll need to remember to get going are:
progress:indeterminate
progress::-webkit-progress-bar
progress::-moz-progress-bar
progress::-webkit-progress-value
Armed with this knowledge - how do we change the background-color
of... whatever any of that is?
progress::-webkit-progress-bar {
background-color: yellow;
}
progress::-webkit-progress-value {
background-color: yellow;
}
progress::-moz-progress-bar {
background-color: yellow;
}
Nothing happened? Hmm, no, guess it's not that.
The finished version
At this point I'm just like... screw it! I tried! (╯°□°)╯︵ ┻━┻
If it's going to be this hard I'll just bypass all that nonsense and do something that's readable and will work regardless of pseudo nonsense!
I ended up reaching for the trusty :after
pseudo-element. In this case it also enables me to do some nice animations, so win-win!
:where(progress) {
--_accent-color: var(--primary);
--_bg-color: var(
--surface-tonal,
light-dark(oklch(95% 0.01 255 / 1), oklch(31% 0.01 255 / 1))
);
appearance: none;
background-color: var(--_bg-color);
border-radius: var(--border-radius);
border: 0;
display: inline-block;
height: var(--size-2);
overflow: hidden;
position: relative;
vertical-align: baseline;
width: 100%;
&::-webkit-progress-bar {
border-radius: var(--border-radius);
background: none;
}
&[value]::-webkit-progress-value {
background-color: var(--_accent-color);
@media (prefers-reduced-motion: no-preference) {
transition: inline-size 0.2s var(--ease-out-4);
}
}
&::-moz-progress-bar {
background-color: var(--_accent-color);
}
}
@media (prefers-reduced-motion: no-preference) {
progress:indeterminate {
background-color: var(--_bg-color);
&::after {
animation: indeterminate 2s linear infinite;
background-color: var(--_accent-color);
content: "";
inset: 0 auto 0 0;
position: absolute;
will-change: inset-inline-start, inset-inline-end;
}
&[value]::-webkit-progress-value {
background-color: transparent;
}
&::-moz-progress-bar {
background-color: transparent;
}
}
}
[dir="rtl"] {
@media (prefers-reduced-motion: no-preference) {
:where(progress):indeterminate {
animation-direction: reverse;
}
}
}
[dir="rtl"] {
@media (prefers-reduced-motion: no-preference) {
:where(progress):indeterminate::after {
animation-direction: reverse;
}
}
}
@keyframes indeterminate {
0% {
left: -200%;
right: 100%;
}
60% {
left: 107%;
right: -8%;
}
100% {
left: 107%;
right: -8%;
}
}
<progress></progress>