Open demo

CSS code

HTML
<div><span class="lbl">Striped</span><div class="card">
<div class="sb">
  <div class="sb-head"><b id="sb-mail">Migrating mailboxes</b><span aria-hidden="true">45%</span></div>
  <div class="sb-bar striped" role="progressbar" aria-labelledby="sb-mail" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="--v:45"><i></i></div>
  <p class="sb-note">412 of 916 mailboxes moved to the new server</p>
</div></div></div>

<div><span class="lbl">Animated stripes</span><div class="card">
<div class="sb">
  <div class="sb-head"><b id="sb-render">Rendering launch-teaser.mp4</b><span aria-hidden="true">68%</span></div>
  <div class="sb-bar striped moving" role="progressbar" aria-labelledby="sb-render" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100" aria-valuetext="68%, about 1 minute left" style="--v:68"><i></i></div>
  <p class="sb-note">About 1 minute left · 1080p, H.264</p>
</div></div></div>

<div><span class="lbl">Indeterminate</span><div class="card">
<div class="sb">
  <div class="sb-head"><b id="sb-export">Preparing your export</b></div>
  <div class="sb-bar indet" role="progressbar" aria-labelledby="sb-export" aria-describedby="sb-export-note" aria-valuemin="0" aria-valuemax="100"><i></i></div>
  <p class="sb-note" id="sb-export-note">Collecting 1,284 invoices from 2025. We will email you when the file is ready.</p>
</div></div></div>

<div><span class="lbl">Glint</span><div class="card">
<div class="sb">
  <div class="sb-head"><b id="sb-update">Installing updates</b><span aria-hidden="true">3 of 7</span></div>
  <div class="sb-bar glint" role="progressbar" aria-labelledby="sb-update" aria-valuenow="3" aria-valuemin="0" aria-valuemax="7" aria-valuetext="3 of 7 updates installed" style="--v:43"><i></i></div>
  <p class="sb-note">Now installing: Security patch 2026-09</p>
</div></div></div>
CSS
/* Striped, animated and indeterminate progress bars. role="progressbar" with aria-value* on .sb-bar; --v (0 to 100)
   sets the fill width. The indeterminate bar leaves out aria-valuenow, which is how ARIA marks an unknown amount.
   Every loop stops under prefers-reduced-motion; the indeterminate bar then shows a still, full-width stripe. */
.sb{--sb-accent:#0f766e;--sb-track:#dbe6e3;--sb-h:14px;display:grid;gap:10px}
.sb-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;font-size:.92rem}
.sb-head b{font-weight:600;color:#15302c}
.sb-head span{font-weight:700;color:#0f766e;font-variant-numeric:tabular-nums}
.sb-note{font-size:.84rem;color:#4f6461}
.sb-bar{--v:0;position:relative;height:var(--sb-h);border-radius:999px;background:var(--sb-track);overflow:hidden}
.sb-bar>i{position:relative;display:block;height:100%;width:calc(var(--v) * 1%);border-radius:inherit;background-color:var(--sb-accent);overflow:hidden}
/* stripes: a 45deg gradient tile the size of the bar height */
.sb-bar.striped>i{background-image:linear-gradient(45deg,rgba(255,255,255,.22) 25%,transparent 25% 50%,rgba(255,255,255,.22) 50% 75%,transparent 75%);
  background-size:var(--sb-h) var(--sb-h)}
/* animated: the tile slides one tile width per second */
.sb-bar.moving>i{animation:sb-stripes 1s linear infinite}
@keyframes sb-stripes{from{background-position:var(--sb-h) 0}to{background-position:0 0}}
/* indeterminate: a 40% segment sweeps across the track */
.sb-bar.indet{--sb-h:6px}
.sb-bar.indet>i{position:absolute;inset:0 auto 0 0;width:40%;animation:sb-sweep 1.5s cubic-bezier(.65,.05,.35,1) infinite}
@keyframes sb-sweep{from{transform:translateX(-100%)}to{transform:translateX(250%)}}
/* glint: a soft highlight runs over a normal fill */
.sb-bar.glint>i::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5),transparent);
  transform:translateX(-100%);animation:sb-glint 2.2s ease-in-out infinite}
@keyframes sb-glint{60%,to{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){
  .sb-bar.moving>i,.sb-bar.indet>i,.sb-bar.glint>i::after{animation:none}
  .sb-bar.glint>i::after{display:none}
  .sb-bar.indet>i{width:100%;background:repeating-linear-gradient(45deg,var(--sb-accent) 0 6px,#5eaaa0 6px 12px)}
}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Striped</span><div class="card demo-card"><div class="card-body p-4">
<div class="sb">
  <div class="d-flex justify-content-between align-items-baseline gap-3"><span class="fw-semibold" id="sb-mail">Migrating mailboxes</span><span class="fw-bold sb-val" aria-hidden="true">45%</span></div>
  <div class="progress" role="progressbar" aria-labelledby="sb-mail" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar progress-bar-striped" style="width:45%"></div></div>
  <p class="sb-note mb-0">412 of 916 mailboxes moved to the new server</p>
</div></div></div></div>

<div><span class="lbl">Animated stripes</span><div class="card demo-card"><div class="card-body p-4">
<div class="sb">
  <div class="d-flex justify-content-between align-items-baseline gap-3"><span class="fw-semibold" id="sb-render">Rendering launch-teaser.mp4</span><span class="fw-bold sb-val" aria-hidden="true">68%</span></div>
  <div class="progress" role="progressbar" aria-labelledby="sb-render" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100" aria-valuetext="68%, about 1 minute left"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:68%"></div></div>
  <p class="sb-note mb-0">About 1 minute left · 1080p, H.264</p>
</div></div></div></div>

<div><span class="lbl">Indeterminate</span><div class="card demo-card"><div class="card-body p-4">
<div class="sb">
  <div class="fw-semibold" id="sb-export">Preparing your export</div>
  <div class="progress sb-indet" role="progressbar" aria-labelledby="sb-export" aria-describedby="sb-export-note" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar"></div></div>
  <p class="sb-note mb-0" id="sb-export-note">Collecting 1,284 invoices from 2025. We will email you when the file is ready.</p>
</div></div></div></div>

<div><span class="lbl">Glint</span><div class="card demo-card"><div class="card-body p-4">
<div class="sb">
  <div class="d-flex justify-content-between align-items-baseline gap-3"><span class="fw-semibold" id="sb-update">Installing updates</span><span class="fw-bold sb-val" aria-hidden="true">3 of 7</span></div>
  <div class="progress sb-glint" role="progressbar" aria-labelledby="sb-update" aria-valuenow="3" aria-valuemin="0" aria-valuemax="7" aria-valuetext="3 of 7 updates installed"><div class="progress-bar" style="width:43%"></div></div>
  <p class="sb-note mb-0">Now installing: Security patch 2026-09</p>
</div></div></div></div>
CSS
/* Bootstrap .progress-bar-striped and .progress-bar-animated, restyled through the .progress variables.
   The indeterminate bar (.sb-indet) and the glint (.sb-glint) are the two things Bootstrap has no class for.
   Bootstrap already stops .progress-bar-animated under prefers-reduced-motion; the rules below do the same for the rest. */
.sb{display:grid;gap:10px;font-size:.92rem}
.sb .progress{--bs-progress-height:14px;--bs-progress-bg:#dbe6e3;--bs-progress-bar-bg:#0f766e;--bs-progress-border-radius:999px}
.sb .progress-bar{position:relative;border-radius:inherit}
.sb .progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.22) 25%,transparent 25% 50%,rgba(255,255,255,.22) 50% 75%,transparent 75%)}
.sb .sb-val{color:#0f766e;font-variant-numeric:tabular-nums}
.sb .sb-note{font-size:.84rem;color:#4f6461}
/* indeterminate: a 40% segment sweeps across the track */
.sb .progress.sb-indet{--bs-progress-height:6px;position:relative}
.sb .sb-indet .progress-bar{position:absolute;inset:0 auto 0 0;width:40%;animation:sb-sweep 1.5s cubic-bezier(.65,.05,.35,1) infinite}
@keyframes sb-sweep{from{transform:translateX(-100%)}to{transform:translateX(250%)}}
/* glint: a soft highlight runs over a normal fill */
.sb .sb-glint .progress-bar::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5),transparent);
  transform:translateX(-100%);animation:sb-glint 2.2s ease-in-out infinite}
@keyframes sb-glint{60%,to{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){
  .sb .sb-glint .progress-bar::after{animation:none;display:none}
  .sb .sb-indet .progress-bar{animation:none;width:100%;background:repeating-linear-gradient(45deg,#0f766e 0 6px,#5eaaa0 6px 12px)}
}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#4f6461] uppercase">Striped</span><div class="rounded-2xl border border-[#dbe6e3] bg-white px-6 pt-[22px] pb-6 shadow-[0_1px_2px_rgba(21,48,44,.04)]">
<div class="grid gap-2.5 text-[.92rem]">
  <div class="flex items-baseline justify-between gap-3"><span class="font-semibold" id="sb-mail">Migrating mailboxes</span><span class="font-bold text-sb-accent tabular-nums" aria-hidden="true">45%</span></div>
  <div class="h-3.5 overflow-hidden rounded-full bg-sb-track" role="progressbar" aria-labelledby="sb-mail" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100"><div class="h-full w-[45%] rounded-full bg-sb-accent bg-[linear-gradient(45deg,rgba(255,255,255,.22)_25%,transparent_25%_50%,rgba(255,255,255,.22)_50%_75%,transparent_75%)] bg-[length:14px_14px]"></div></div>
  <p class="text-[.84rem] text-[#4f6461]">412 of 916 mailboxes moved to the new server</p>
</div></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#4f6461] uppercase">Animated stripes</span><div class="rounded-2xl border border-[#dbe6e3] bg-white px-6 pt-[22px] pb-6 shadow-[0_1px_2px_rgba(21,48,44,.04)]">
<div class="grid gap-2.5 text-[.92rem]">
  <div class="flex items-baseline justify-between gap-3"><span class="font-semibold" id="sb-render">Rendering launch-teaser.mp4</span><span class="font-bold text-sb-accent tabular-nums" aria-hidden="true">68%</span></div>
  <div class="h-3.5 overflow-hidden rounded-full bg-sb-track" role="progressbar" aria-labelledby="sb-render" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100" aria-valuetext="68%, about 1 minute left"><div class="h-full w-[68%] animate-sb-stripes rounded-full bg-sb-accent bg-[linear-gradient(45deg,rgba(255,255,255,.22)_25%,transparent_25%_50%,rgba(255,255,255,.22)_50%_75%,transparent_75%)] bg-[length:14px_14px] motion-reduce:animate-none"></div></div>
  <p class="text-[.84rem] text-[#4f6461]">About 1 minute left · 1080p, H.264</p>
</div></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#4f6461] uppercase">Indeterminate</span><div class="rounded-2xl border border-[#dbe6e3] bg-white px-6 pt-[22px] pb-6 shadow-[0_1px_2px_rgba(21,48,44,.04)]">
<div class="grid gap-2.5 text-[.92rem]">
  <div class="font-semibold" id="sb-export">Preparing your export</div>
  <div class="relative h-1.5 overflow-hidden rounded-full bg-sb-track" role="progressbar" aria-labelledby="sb-export" aria-describedby="sb-export-note" aria-valuemin="0" aria-valuemax="100"><div class="absolute inset-y-0 left-0 w-2/5 animate-sb-sweep rounded-full bg-sb-accent motion-reduce:w-full motion-reduce:animate-none motion-reduce:bg-[repeating-linear-gradient(45deg,#0f766e_0_6px,#5eaaa0_6px_12px)]"></div></div>
  <p class="text-[.84rem] text-[#4f6461]" id="sb-export-note">Collecting 1,284 invoices from 2025. We will email you when the file is ready.</p>
</div></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#4f6461] uppercase">Glint</span><div class="rounded-2xl border border-[#dbe6e3] bg-white px-6 pt-[22px] pb-6 shadow-[0_1px_2px_rgba(21,48,44,.04)]">
<div class="grid gap-2.5 text-[.92rem]">
  <div class="flex items-baseline justify-between gap-3"><span class="font-semibold" id="sb-update">Installing updates</span><span class="font-bold text-sb-accent tabular-nums" aria-hidden="true">3 of 7</span></div>
  <div class="h-3.5 overflow-hidden rounded-full bg-sb-track" role="progressbar" aria-labelledby="sb-update" aria-valuenow="3" aria-valuemin="0" aria-valuemax="7" aria-valuetext="3 of 7 updates installed"><div class="relative h-full w-[43%] overflow-hidden rounded-full bg-sb-accent after:absolute after:inset-0 after:animate-sb-glint after:bg-[linear-gradient(90deg,transparent,rgba(255,255,255,.5),transparent)] after:content-[''] motion-reduce:after:hidden"></div></div>
  <p class="text-[.84rem] text-[#4f6461]">Now installing: Security patch 2026-09</p>
</div></div></div>
CSS
@theme {
  --color-sb-accent: #0f766e;
  --color-sb-track: #dbe6e3;
  --animate-sb-stripes: sb-stripes 1s linear infinite;
  --animate-sb-sweep: sb-sweep 1.5s cubic-bezier(.65,.05,.35,1) infinite;
  --animate-sb-glint: sb-glint 2.2s ease-in-out infinite;
  @keyframes sb-stripes { from { background-position: 14px 0; } to { background-position: 0 0; } }
  @keyframes sb-sweep { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
  @keyframes sb-glint { 0% { transform: translateX(-100%); } 60%, 100% { transform: translateX(100%); } }
}

About this set

Four progress bars with texture and motion, for work that takes a while. Striped lays a 45 degree stripe tile over the fill, sized to the bar height so the stripes stay square at any size, shown on a mailbox migration. Animated stripes slides the same tile one width per second to show that a video render is still running, the look of Bootstrap’s progress-bar-animated. Indeterminate is for jobs with no known end, such as preparing an export: a 40 percent segment sweeps across a thin track, and the bar leaves out aria-valuenow, which is how ARIA marks an unknown amount. Glint keeps a plain determinate fill and runs a soft highlight across it every couple of seconds, used here for installing 3 of 7 updates with the count in aria-valuetext. Use the stripes when you want to show activity on a known value and the indeterminate bar only until the first real number arrives. Everything is CSS keyframes on background-position and transform, and each loop stops under prefers-reduced-motion, where the indeterminate bar turns into a still, full-width stripe so it still reads as busy.

What’s included

  • Striped, animated stripes, indeterminate and glint variants
  • Stripe tile sized to the bar height
  • Indeterminate bar without aria-valuenow, as ARIA specifies
  • Every loop stops under prefers-reduced-motion
  • Still, full-width stripe as the reduced-motion indeterminate state
  • Bootstrap version uses .progress-bar-striped and .progress-bar-animated

Accessibility

Each bar has role="progressbar" and a visible label tied to it with aria-labelledby; the determinate bars carry aria-valuenow and, where the number needs context, aria-valuetext such as 3 of 7 updates installed. The indeterminate bar omits aria-valuenow and points to its explanation with aria-describedby. Under prefers-reduced-motion the stripes, sweep and glint stop, and the indeterminate bar becomes a still striped track.

Customise it

Change --sb-accent, --sb-track and --sb-h on .sb in the plain CSS version; the stripe tile follows --sb-h. In Bootstrap set --bs-progress-height and --bs-progress-bar-bg on .progress. In Tailwind edit the sb- theme colours and the --animate-sb-* durations in @theme.