Skip to content

Progress Bar Examples

The examples below assume that you have imported the component and set up the theme.

Basic

Set value within the min/max range, and the fill animates to every assigned value. The bar takes the full width of its container.

<div class="bar-basic">
<sv-progress-bar aria-label="Upload progress" value="65"></sv-progress-bar>
</div>
<style>
.bar-basic {
display: flex;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
inline-size: min(100%, 20rem);
}
}
</style>

Indeterminate

Without a value, the bar is indeterminate and a short fill slides along the track until one is assigned.

Reduced Motion

Your system is set to reduce motion, so the fill sits still in the middle of the track.

<div class="bar-indeterminate">
<sv-progress-bar aria-label="Loading"></sv-progress-bar>
<sv-progress-bar aria-label="Loading" vertical></sv-progress-bar>
</div>
<style>
.bar-indeterminate {
display: flex;
flex-wrap: wrap;
gap: 2em;
align-items: center;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
inline-size: min(100%, 16rem);
}
sv-progress-bar[vertical] {
inline-size: auto;
block-size: 8rem;
}
}
</style>

Label

Slotted content sits in a row before the track, with the first and last element pushed to opposite ends. A child with data-percentage or data-value renders the current number, and the attribute's number sets the decimal places. A data-value-text element supplies aria-valuetext, since slotted content is hidden from assistive technology. Set flex-direction: column-reverse on the bar to move the content below the track.

Uploading % Uploading %
<div class="bar-label">
<sv-progress-bar aria-label="Upload progress" value="50">
<span>Uploading</span>
<span class="value" data-value-text>
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
<sv-progress-bar class="labels-below" aria-label="Upload progress" value="50">
<span>Uploading</span>
<span class="value" data-value-text>
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
</div>
<style>
.bar-label {
display: grid;
gap: 2em;
justify-items: center;
padding-block: 1em;
sv-progress-bar {
inline-size: min(100%, 20rem);
font-size: 0.875rem;
}
.labels-below {
flex-direction: column-reverse;
}
.value {
display: flex;
}
}
</style>

Fill Content

Content in the fill slot is centered inside the filled portion. Make the track and the fill thick enough for the text.

%
<div class="bar-fill-content">
<sv-progress-bar aria-label="Upload progress" value="50">
<span class="value" data-value-text slot="fill">
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
</div>
<style>
.bar-fill-content {
display: flex;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
--sv-progress-bar-thickness: 1.25rem;
inline-size: min(100%, 20rem);
}
.value {
display: flex;
font-size: 0.75rem;
color: var(--sv-accent-content);
}
}
</style>

Buffer

buffer-value shows a second, fainter fill behind the first one, for content that loads ahead of the current position. It uses the same range as value, and buffer-percentage sets it as a percentage of that range.

<div class="bar-buffer">
<sv-progress-bar aria-label="Video playback" buffer-value="60" value="30"></sv-progress-bar>
</div>
<style>
.bar-buffer {
display: flex;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
inline-size: min(100%, 20rem);
}
}
</style>

Vertical

vertical turns the bar upright and fills it from the bottom. A vertical bar has no height of its own. Give it one. With flex-direction: row and align-items: stretch, the content sits beside the track instead of above it.

% %
<div class="bar-vertical">
<sv-progress-bar aria-label="Battery level" value="70" vertical>
<span class="value" data-value-text>
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
<sv-progress-bar class="label-side" aria-label="Battery level" value="70" vertical>
<span class="value" data-value-text>
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
</div>
<style>
.bar-vertical {
display: flex;
gap: 3em;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
block-size: 10rem;
font-size: 0.875rem;
}
.value {
display: flex;
}
.label-side {
flex-direction: row;
align-items: stretch;
.value {
writing-mode: sideways-lr;
}
}
}
</style>

Download

Assign value from JavaScript whenever progress arrives, and the fill animates to it on its own.

Downloading %
<div class="bar-download">
<sv-progress-bar class="download-bar" aria-label="Download progress" value="0">
<span>Downloading</span>
<span class="value" data-value-text>
<span data-percentage="0"></span>
<span>%</span>
</span>
</sv-progress-bar>
</div>
<script type="application/x-typescript" module>
const bar = document.querySelector<ProgressBar>(".download-bar")!;
let speed = 100;
function tick() {
const current = bar.value ?? 0;
const next = current >= bar.max ? 0 : current + 1;
bar.value = next;
if (Math.random() < 0.1) {
speed = 20 + Math.random() * 380;
}
setTimeout(tick, next >= bar.max ? 1500 : speed);
}
tick();
</script>
<style>
.bar-download {
display: flex;
justify-content: center;
padding-block: 1em;
sv-progress-bar {
inline-size: min(100%, 20rem);
font-size: 0.875rem;
}
.value {
display: flex;
}
}
</style>

Styling

Custom properties control the thickness, corner radius, and colors of the track and the fill.

--sv-progress-bar-thickness
--sv-progress-bar-fill-thickness
--sv-progress-bar-track-thickness
--sv-progress-bar-rad
color tokens
<div class="bar-styling">
<div class="variant">
<sv-progress-bar class="thick" aria-label="Thick bar" value="60"></sv-progress-bar>
<span>--sv-progress-bar-thickness</span>
</div>
<div class="variant">
<sv-progress-bar class="thin-fill" aria-label="Thin fill on a thick track" value="60"></sv-progress-bar>
<span>--sv-progress-bar-fill-thickness</span>
</div>
<div class="variant">
<sv-progress-bar class="thick-fill" aria-label="Thick fill on a thin track" value="60"></sv-progress-bar>
<span>--sv-progress-bar-track-thickness</span>
</div>
<div class="variant">
<sv-progress-bar class="square" aria-label="Square bar" value="60"></sv-progress-bar>
<span>--sv-progress-bar-rad</span>
</div>
<div class="variant">
<sv-progress-bar class="tinted" aria-label="Amber bar" value="60"></sv-progress-bar>
<span>color tokens</span>
</div>
</div>
<style>
.bar-styling {
display: grid;
gap: 1.5em;
justify-items: center;
padding-block: 1em;
.variant {
display: grid;
gap: 0.4em;
justify-items: center;
inline-size: min(100%, 20rem);
span {
font-size: 0.8em;
color: var(--sv-gray-3);
}
}
sv-progress-bar {
inline-size: 100%;
}
.thick {
--sv-progress-bar-thickness: 12px;
}
.thin-fill {
--sv-progress-bar-track-thickness: 12px;
--sv-progress-bar-fill-thickness: 4px;
}
.thick-fill {
--sv-progress-bar-track-thickness: 4px;
--sv-progress-bar-fill-thickness: 12px;
}
.square {
--sv-progress-bar-rad: 0;
}
.tinted {
--sv-progress-bar-fill-clr: #f59e0b;
--sv-progress-bar-track-clr: color-mix(in srgb, #f59e0b 20%, transparent);
}
}
</style>