Progress
Progress indicators show the status of ongoing processes.
Progress Bar
<div style="display: flex; flex-direction: column; gap: var(--space-4); width: 100%;">
<div class="Progress">
<div class="Progress-bar" style="width: 25%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 50%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 75%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 100%;"></div>
</div>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 50%;"></div>
</div>
With Label
Uploading...
67%
<div style="width: 100%;">
<div style="display: flex; justify-content: space-between; margin-bottom: var(--space-2); font-size: 0.9rem;">
<span>Uploading...</span>
<span>67%</span>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 67%;"></div>
</div>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Uploading...</span>
<span>67%</span>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 67%;"></div>
</div>
Sizes
Small
Default
Large
<div style="display: flex; flex-direction: column; gap: var(--space-4); width: 100%;">
<div>
<small style="color: var(--fg-3);">Small</small>
<div class="Progress Progress--small" style="margin-top: var(--space-1);">
<div class="Progress-bar" style="width: 60%;"></div>
</div>
</div>
<div>
<small style="color: var(--fg-3);">Default</small>
<div class="Progress" style="margin-top: var(--space-1);">
<div class="Progress-bar" style="width: 60%;"></div>
</div>
</div>
<div>
<small style="color: var(--fg-3);">Large</small>
<div class="Progress Progress--large" style="margin-top: var(--space-1);">
<div class="Progress-bar" style="width: 60%;"></div>
</div>
</div>
</div>
<div class="Progress Progress--small">...</div>
<div class="Progress">...</div>
<div class="Progress Progress--large">...</div>
Variants
<div style="display: flex; flex-direction: column; gap: var(--space-3); width: 100%;">
<div class="Progress">
<div class="Progress-bar" style="width: 60%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar Progress-bar--success" style="width: 100%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar Progress-bar--warning" style="width: 45%;"></div>
</div>
<div class="Progress">
<div class="Progress-bar Progress-bar--error" style="width: 30%;"></div>
</div>
</div>
<div class="Progress-bar Progress-bar--success"></div>
<div class="Progress-bar Progress-bar--warning"></div>
<div class="Progress-bar Progress-bar--error"></div>
Indeterminate
For unknown duration processes.
<div class="Progress">
<div class="Progress-bar Progress-bar--indeterminate"></div>
</div>
<div class="Progress">
<div class="Progress-bar Progress-bar--indeterminate"></div>
</div>
Spinner
For inline loading states.
<div style="display: flex; gap: var(--space-6); align-items: center;">
<div class="Spinner Spinner--small"></div>
<div class="Spinner"></div>
<div class="Spinner Spinner--large"></div>
</div>
<div class="Spinner Spinner--small"></div>
<div class="Spinner"></div>
<div class="Spinner Spinner--large"></div>
Button with Spinner
<div style="display: flex; gap: var(--space-4);">
<button class="Button Button--primary" disabled>
<div class="Spinner Spinner--small Spinner--light"></div>
<span>Saving...</span>
</button>
<button class="Button" disabled>
<div class="Spinner Spinner--small"></div>
<span>Loading...</span>
</button>
</div>
<button class="Button Button--primary" disabled>
<div class="Spinner Spinner--small Spinner--light"></div>
<span>Saving...</span>
</button>
Full Page Loader
Loading...
<div style="height: 200px; display: flex; align-items: center; justify-content: center; background: var(--bg-s); border-radius: var(--r-m);">
<div style="text-align: center;">
<div class="Spinner Spinner--large"></div>
<p style="margin-top: var(--space-3); color: var(--fg-3); font-size: 0.9rem;">Loading...</p>
</div>
</div>
<div class="page-loader">
<div class="Spinner Spinner--large"></div>
<p>Loading...</p>
</div>
Common Patterns
File Upload Progress
report-2024.pdf
2.4 MB of 5.1 MB
<div style="width: 100%; max-width: 400px; padding: var(--space-4); border: 1px solid var(--bd); border-radius: var(--r-m);">
<div style="display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3);">
<i class="ph ph-file-arrow-up" style="font-size: 1.5rem; color: var(--accent);"></i>
<div style="flex: 1;">
<div style="font-weight: 500; font-size: 0.9rem;">report-2024.pdf</div>
<div style="font-size: 0.8rem; color: var(--fg-3);">2.4 MB of 5.1 MB</div>
</div>
<span style="font-weight: 600; font-size: 0.9rem;">47%</span>
</div>
<div class="Progress">
<div class="Progress-bar" style="width: 47%;"></div>
</div>
</div>
Multi-step Form Progress
Step 2 of 4
50% complete
<div style="width: 100%; max-width: 500px;">
<div style="display: flex; justify-content: space-between; margin-bottom: var(--space-2); font-size: 0.85rem;">
<span style="font-weight: 500;">Step 2 of 4</span>
<span style="color: var(--fg-3);">50% complete</span>
</div>
<div class="Progress Progress--small">
<div class="Progress-bar" style="width: 50%;"></div>
</div>
</div>
Inline Loading Button
<div style="display: flex; gap: var(--space-3); flex-wrap: wrap;">
<button class="Button Button--primary" disabled>
<div class="Spinner Spinner--small Spinner--light"></div>
<span>Saving...</span>
</button>
<button class="Button" disabled>
<div class="Spinner Spinner--small"></div>
<span>Loading...</span>
</button>
<button class="Button Button--primary">
<span>Save Changes</span>
</button>
</div>
Dashboard Metric
Storage
7.2 GB of 10 GB used
<div style="width: 100%; max-width: 300px; padding: var(--space-4); background: var(--bg-s); border-radius: var(--r-m);">
<div style="font-weight: 600; margin-bottom: var(--space-1);">Storage</div>
<div style="font-size: 0.85rem; color: var(--fg-3); margin-bottom: var(--space-3);">7.2 GB of 10 GB used</div>
<div class="Progress">
<div class="Progress-bar Progress-bar--warning" style="width: 72%;"></div>
</div>
</div>
Full Page Loading
Loading your dashboard...
<div style="height: 180px; display: flex; align-items: center; justify-content: center; background: var(--bg-s); border-radius: var(--r-m);">
<div style="text-align: center;">
<div class="Spinner Spinner--large"></div>
<p style="margin-top: var(--space-3); color: var(--fg-3); font-size: 0.9rem;">Loading your dashboard...</p>
</div>
</div>
Customization
Override progress styling with CSS custom properties:
/* Custom progress bar color */
.Progress {
--progress-bg: oklch(90% 0 0);
--progress-fill: oklch(55% 0.2 260);
}
.Progress {
background: var(--progress-bg);
}
.Progress-bar {
background: var(--progress-fill);
}
Striped Progress
.Progress-bar--striped {
background-image: linear-gradient(
45deg,
rgba(255,255,255,.15) 25%,
transparent 25%,
transparent 50%,
rgba(255,255,255,.15) 50%,
rgba(255,255,255,.15) 75%,
transparent 75%
);
background-size: 1rem 1rem;
}
Custom Spinner Color
.Spinner--branded {
border-color: oklch(80% 0.05 260);
border-top-color: oklch(55% 0.2 260);
}
Theming
[data-theme="dark"] .Progress {
background: oklch(25% 0 0);
}
[data-theme="dark"] .Progress-bar {
background: oklch(65% 0.18 260);
}
API Reference
| Class | Description |
|---|---|
.Progress | Base progress bar container |
.Progress--small | Smaller height track (4px) |
.Progress--large | Larger height track (12px) |
.Progress-bar | Fill indicator element |
.Progress-bar--success | Green completion variant |
.Progress-bar--warning | Yellow warning variant |
.Progress-bar--error | Red error variant |
.Progress-bar--indeterminate | Animated unknown-duration progress |
.Spinner | Base spinner element |
.Spinner--small | Small spinner (16px) |
.Spinner--large | Large spinner (32px) |
.Spinner--light | Light color for dark backgrounds |
Attributes
| Attribute | Description |
|---|---|
role="progressbar" | Identifies the element as a progress bar |
aria-valuenow | Current progress value |
aria-valuemin | Minimum value (typically 0) |
aria-valuemax | Maximum value (typically 100) |
aria-label | Describes what the progress represents |
CSS Reference
/* Base progress bar */
.Progress {
width: 100%;
height: 8px;
background: var(--bd);
border-radius: var(--r-full);
overflow: hidden;
}
.Progress-bar {
height: 100%;
background: var(--accent);
border-radius: var(--r-full);
transition: width 0.3s ease;
}
/* Sizes */
.Progress--small { height: 4px; }
.Progress--large { height: 12px; }
/* Variants */
.Progress-bar--success { background: oklch(55% 0.15 150); }
.Progress-bar--warning { background: oklch(70% 0.15 80); }
.Progress-bar--error { background: oklch(55% 0.2 25); }
/* Indeterminate */
.Progress-bar--indeterminate {
width: 30% !important;
animation: progress-indeterminate 1.5s ease-in-out infinite;
}
@keyframes progress-indeterminate {
0% { transform: translateX(-100%); }
100% { transform: translateX(400%); }
}
/* Spinner */
.Spinner {
width: 24px;
height: 24px;
border: 3px solid var(--bd);
border-top-color: var(--accent);
border-radius: 50%;
animation: spinner-rotate 0.8s linear infinite;
}
.Spinner--small { width: 16px; height: 16px; border-width: 2px; }
.Spinner--large { width: 32px; height: 32px; border-width: 4px; }
.Spinner--light { border-color: rgba(255,255,255,0.3); border-top-color: white; }
@keyframes spinner-rotate {
to { transform: rotate(360deg); }
}
Accessibility
Screen Reader Guidance
Use role="progressbar" with aria-valuenow, aria-valuemin, and aria-valuemax for determinate progress. For indeterminate progress, omit aria-valuenow. Always provide aria-label or visible text to describe what is loading.
<div class="Progress" role="progressbar"
aria-valuenow="67" aria-valuemin="0" aria-valuemax="100"
aria-label="Upload progress">
<div class="Progress-bar" style="width: 67%;"></div>
</div>
Spinner Accessibility
Spinners should have aria-label="Loading" or be associated with visible text. Use aria-busy="true" on the container being loaded.
<div aria-busy="true">
<div class="Spinner" role="status" aria-label="Loading"></div>
</div>
ARIA Attributes
role="progressbar"on the progress containeraria-valuenow/aria-valuemin/aria-valuemaxfor determinate barsaria-labelto describe the processaria-busy="true"on content regions while loading- Announce completion to screen readers via
aria-liveregion
Best Practices
Do
- ✓ Show percentage when known — Users want to estimate remaining time
- ✓ Use indeterminate for unknown durations — Communicates activity without misleading
- ✓ Provide descriptive labels — “Uploading report.pdf…” beats a bare progress bar
- ✓ Use color variants for status — Green for success, red for errors, yellow for warnings
- ✓ Keep users informed — Describe what’s happening during long operations
- ✓ Use spinners for inline loading — Small spinners inside buttons for quick actions
Don’t
- ✗ Show fake progress — Don’t animate progress that doesn’t reflect reality
- ✗ Leave users without feedback — Any action over 1 second needs an indicator
- ✗ Use spinners for content loading — Skeleton screens are better for layout-preserving loads
- ✗ Block interaction silently — Always show why the UI is unresponsive
- ✗ Omit ARIA attributes — Screen reader users need progress information too
- ✗ Stack multiple progress bars — One clear indicator is better than several competing ones