Progress
A horizontal bar showing how far along a task is — a file upload, a multi-step form, a loading process. The filled portion reflects the current value against a maximum, and changes animate smoothly so progress reads as motion rather than jumps.
Usage
Pass a value from 0 to 100. The bar fills to that percentage.
<script setup lang="ts">
import { Progress } from "@dlbcodes/ui";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<Progress :value="25" />
<Progress :value="50" />
<Progress :value="75" />
<Progress :value="100" />
</div>
</template>
Animated
Because the fill transitions, updating value over time animates the bar — handy for uploads or any task you poll.
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { Progress, Button } from "@dlbcodes/ui";
const value = ref(10);
let timer: ReturnType<typeof setInterval> | undefined;
const start = (): void => {
value.value = 0;
clearInterval(timer);
timer = setInterval(() => {
value.value += 10;
if (value.value >= 100) clearInterval(timer);
}, 400);
};
onMounted(start);
onUnmounted(() => clearInterval(timer));
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-3">
<Progress :value="value" />
<div class="flex items-center justify-between">
<span class="text-sm text-text-secondary">{{ value }}%</span>
<Button variant="outline" size="sm" @click="start">Restart</Button>
</div>
</div>
</template>
Custom maximum
Set max to measure against something other than 100 — useful for "step N of M" progress.
<script setup lang="ts">
import { Progress } from "@dlbcodes/ui";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<Progress :value="3" :max="5" />
<span class="text-sm text-text-secondary">Step 3 of 5</span>
</div>
</template>
Indeterminate
When you don't know how far along the task is, omit value. The bar shows a segment sliding across the track instead of a fill.
<Progress label="Loading results" />For users who prefer reduced motion, the sliding segment is replaced by a gentle pulse of the full bar.
Labelling
A progress bar needs an accessible name so screen readers can announce what is progressing, not just the percentage. Pass label with a short description:
<Progress :value="86" label="Storage used" />If a visible caption already names the bar, point at it with aria-labelledby instead, so the accessible name and the on-screen text stay in sync:
<span id="upload-label">Uploading…</span>
<Progress :value="60" aria-labelledby="upload-label" />Appearance
The track is the theme's --muted color and the fill is --brand, so a progress bar follows your theme, including dark mode. Both ends are fully rounded regardless of --radius. See Theming.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Current progress (0–max). Omit for an indeterminate (animated) bar. Clamped to range. |
max | number | 100 | The value representing 100% complete. |
label | string | — | Accessible name for the bar, announced by screen readers (sets aria-label). |
class | string | — | Classes merged onto the track — use for height or width. |
Any other attribute (e.g. aria-labelledby) forwards to the progressbar element.
Accessibility
- The bar has
role="progressbar"witharia-valuemin,aria-valuemax, andaria-valuenow, so assistive tech announces the current progress. - Give every bar a name. A
progressbarrole without an accessible name is announced generically and is unusable on a screen reader. Passlabel(oraria-labelledbyto reference visible text). There is intentionally no default name — a generic one would hide the need for a meaningful one. - An indeterminate bar (no
value) leaves outaria-valuenow, which is how ARIA tells assistive tech that the amount of progress is unknown. - Values are clamped to the
0–maxrange, so out-of-range input won't break the bar visually or in the ARIA values.