Progress
A visual indicator that shows how much of a task has been completed.
import { component$, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progress = 30;
return (
<Progress.Root value={progress} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
✨ Features
- Follows the WAI-Aria design pattern
- Supports indeterminate state for unknown progress
- Allows dynamic updatess
- Customizable value labels for accessibility
Defining the range
Min
The min
prop sets a minimum starting point for the progress bar.
🎗️ Charity Fundraiser
Funding goal: $10000
import { component$, useSignal, useStyles$, $ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const fundraisingGoal = 10000;
const amountRaised = 5000;
const minGoal = useSignal(2000);
const space = { margin: '1rem' };
const incrementMin = $(() => {
if (minGoal.value < amountRaised) minGoal.value += 500;
});
const decrementMin = $(() => {
if (minGoal.value > 0) minGoal.value -= 500;
});
return (
<div style={{ userSelect: 'none', display: 'contents' }}>
<p style={space}>🎗️ Charity Fundraiser</p>
<div>
Initial funding:
<button onClick$={decrementMin} style={space}>
-
</button>
<span>${minGoal.value}</span>
<button onClick$={incrementMin} style={space}>
+
</button>
</div>
<div style={space}>Amount raised: ${amountRaised}</div>
<Progress.Root
value={amountRaised}
max={fundraisingGoal}
min={minGoal.value}
class="progress"
>
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
<p style={space}>Funding goal: ${fundraisingGoal}</p>
</div>
);
});
// internal
import styles from '../snippets/progress.css?inline';
Adding a value
To set a value for the progress bar, use the value
prop in the Progress.Root
component.
import { component$, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progress = 30;
return (
<Progress.Root value={progress} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
Max
The max
prop defines the upper limit of the progress bar.
🧁 Tiara's Treats
Number of eaten treats: 20
import { component$, useSignal, useStyles$, $ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const initialNumTreats = 25;
const totalTreats = useSignal(initialNumTreats);
const treatsEaten = 20;
const space = { margin: '1rem' };
const increment = $(() => totalTreats.value++);
const decrement = $(() => {
if (totalTreats.value > 20) totalTreats.value--;
});
return (
<>
<p style={space}>🧁 Tiara's Treats</p>
<div>
Total treats:
<button onClick$={decrement} style={space}>
-
</button>
<span>{totalTreats.value}</span>
<button onClick$={increment} style={space}>
+
</button>
</div>
<Progress.Root
value={Number(treatsEaten)}
max={Number(totalTreats.value)}
class="progress"
>
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
<p style={space}>Number of eaten treats: {treatsEaten}</p>
</>
);
});
// internal
import styles from '../snippets/progress.css?inline';
Status
In progress
When the task is ongoing, the progress component reflects this state.
import { component$, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progress = 30;
return (
<Progress.Root value={progress} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
The data-progress="in-progress"
attribute is applied to the progress and its indicator.
Complete
When the task is finished, the data-progress="complete"
attribute is applied.
import { component$, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
return (
<Progress.Root value={100} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
Indeterminate progress
If the progress is uncertain, the bar should be in an indeterminate state. This occurs when the value is null
.
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progressSig = useSignal(null);
return (
<Progress.Root value={progressSig.value} class="progress">
<Progress.Indicator class="progress-indicator indeterminate" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
In this state, the progress and its indicator will have the data-progress="indeterminate"
attribute.
Indeterminate Example CSS
@media (prefers-reduced-motion: no-preference) {
.progress-indicator.indeterminate {
width: 30%;
animation: indeterminate-slide 2s infinite cubic-bezier(0.37, 0, 0.63, 1);
}
}
@keyframes indeterminate-slide {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(400%);
}
}
State
Initial Progress
To set the initial progress on page load, you can pass a value
prop to the Progress.Root
component.
import { component$, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progress = 30;
return (
<Progress.Root value={progress} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
);
});
// internal
import styles from '../snippets/progress.css?inline';
Dynamic Progress Updates
To update the progress bar in real-time, pass a signal to the bind:value
prop.
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progressSig = useSignal(30);
return (
<>
<Progress.Root bind:value={progressSig} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
<button onClick$={() => (progressSig.value = 70)}>Change progress</button>
</>
);
});
// internal
import styles from '../snippets/progress.css?inline';
The progress bar can also be used in combination with other Qwik UI headless components, such as the Carousel component when the user navigates through the slides.
CSR
Similar to other Qwik UI headless components, the Progress Bar automatically renders based on its environment. The previous progress bars are rendered on the server.
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
useStyles$(styles);
const progress = 30;
const isRendered = useSignal(false);
return (
<>
<button onClick$={() => (isRendered.value = true)}>Render the progress bar</button>
{isRendered.value && (
<Progress.Root value={progress} class="progress">
<Progress.Indicator class="progress-indicator" />
</Progress.Root>
)}
</>
);
});
// internal
import styles from '../snippets/progress.css?inline';
The example above shows the progress bar being rendered on the client when the isRendered
signal is set to true
.
Building blocks
import { component$ } from '@builder.io/qwik';
import { Progress } from '@qwik-ui/headless';
export default component$(() => {
const progress = 30;
return (
<Progress.Root value={progress} class="progress">
<Progress.Indicator
class="progress-indicator"
style={{
transform: `translateX(-${100 - progress}%)`,
}}
/>
</Progress.Root>
);
});
🎨 Anatomy
Component | Description |
Progress.Root | The root container for the progress |
Progress.Indicator | Displays the progression process |
Example CSS
.progress {
height: 1.75rem;
width: 100%;
overflow: hidden;
border-radius: calc(var(--border-radius) / 2);
border: 2px dotted hsl(var(--primary));
}
.progress-indicator {
height: 100%;
width: 100%;
background-color: hsl(var(--primary));
}
@keyframes loading-flash {
0% {
opacity: 0.1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}
@media (prefers-reduced-motion: no-preference) {
.progress-indicator.indeterminate {
width: 30%;
animation: indeterminate-slide 2s infinite cubic-bezier(0.37, 0, 0.63, 1);
}
}
@keyframes indeterminate-slide {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(400%);
}
}
.progress-example-input {
border-radius: calc(var(--border-radius) / 2);
border: 2px dotted hsl(var(--primary));
margin-left: 0.5rem;
max-width: 3rem;
padding: 0.25rem;
outline: none;
}
API
Progress.Root
Prop | Type | Description |
---|---|---|
value | union number | null | Current value of the progress bar. Can be null for indeterminate state. |
min | number | Minimum starting point for the progress bar. |
max | number | Maximum value of the progress bar. |
getValueLabel | function | A function to get the accessible label for the current value. If not provided, the label will be shown as a percentage of the range. |
bind:value | Signal Signal<number | null> | A signal to bind the current value of the progress bar for reactive updates. |