Progress
Displays how far a task has got.
- v1.0.0
- WAI-ARIA: progressbar role
- SSR
- Zoneless
- 1.0 kB gzip
import { Component } from '@angular/core';
import { Progress, ProgressIndicator } from '@/ui/progress/progress';
/** A progress bar named by a visible label. */
@Component({
selector: 'example-progress-demo',
imports: [Progress, ProgressIndicator],
template: `
<div class="grid w-full max-w-sm gap-2">
<span id="progress-demo-label" class="text-sm font-medium"
>Setting up your workspace</span
>
<div ndProgress [value]="66" aria-labelledby="progress-demo-label">
<div ndProgressIndicator></div>
</div>
</div>
`,
})
export class ProgressDemoExample {}
A progress bar shows how far a task has got: an upload, an installation,
the steps of a form. It's announced as a progressbar with its value and
percentage, and fills from the start side, so from the right in
right-to-left layouts. Leave value empty while you can't say how far
it has got, and it pulses instead.
Installation
npx @ndop/cli add progresspnpm dlx @ndop/cli add progressyarn dlx @ndop/cli add progressbunx @ndop/cli add progressInstall the dependencies
npm install @ndop/primitives @ndop/corepnpm add @ndop/primitives @ndop/coreyarn add @ndop/primitives @ndop/corebun add @ndop/primitives @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/progress/progress.tsimport { computed, Directive, inject, input } from '@angular/core'; import { ND_PROGRESS, NdProgress, NdProgressIndicator, } from '@ndop/primitives/progress'; import { cn } from '@/lib/utils'; /** * A progress bar (`@ndop/primitives/progress`): how far a task has got, * from 0 to `max`, or indeterminate while `value` is `null`. Give it a * name with `aria-label` or `aria-labelledby`, and put a * `ndProgressIndicator` inside. * * @example * ```html * <div ndProgress [value]="uploaded()" aria-label="Upload"> * <div ndProgressIndicator></div> * </div> * ``` */ @Directive({ selector: '[ndProgress]', hostDirectives: [{ directive: NdProgress, inputs: ['value', 'max'] }], host: { 'data-slot': 'progress', '[class]': 'classes()' }, }) export class Progress { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'relative h-2 w-full overflow-hidden rounded-full bg-primary/20', this.class(), ), ); } /** * The filled part of a progress bar. It fills from the start side, so * from the right in right-to-left layouts, and pulses across the whole * bar while indeterminate. * * @example * ```html * <div ndProgressIndicator></div> * ``` */ @Directive({ selector: '[ndProgressIndicator]', hostDirectives: [NdProgressIndicator], host: { 'data-slot': 'progress-indicator', '[style.width.%]': 'progress.percent()', '[class]': 'classes()', }, }) export class ProgressIndicator { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly progress = inject(ND_PROGRESS); protected readonly classes = computed(() => cn( 'h-full flex-1 bg-primary transition-all', 'data-[state=indeterminate]:w-full data-[state=indeterminate]:animate-pulse', this.class(), ), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { Progress, ProgressIndicator } from '@/ui/progress/progress';<div class="grid w-full max-w-sm gap-2">
<span id="progress-demo-label" class="text-sm font-medium"
>Setting up your workspace</span
>
<div ndProgress [value]="66" aria-labelledby="progress-demo-label">
<div ndProgressIndicator></div>
</div>
</div>Examples
Progress upload
Files uploading, each with its progress and percentage.
- report.pdf100%
- photos.zip45%
- video.mp4Waiting
import { Component } from '@angular/core';
import { Progress, ProgressIndicator } from '@/ui/progress/progress';
/**
* Files uploading, each with its own bar and percentage; the last one
* waits to start, so its bar is indeterminate.
*/
@Component({
selector: 'example-progress-upload',
imports: [Progress, ProgressIndicator],
template: `
<ul class="grid w-full max-w-sm gap-4">
@for (file of files; track file.name) {
<li class="grid gap-2">
<div class="flex justify-between text-sm">
<span [id]="'progress-upload-' + $index" class="font-medium">{{
file.name
}}</span>
<span class="text-muted-foreground">{{
file.value === null ? 'Waiting' : file.value + '%'
}}</span>
</div>
<div
ndProgress
[value]="file.value"
[attr.aria-labelledby]="'progress-upload-' + $index"
>
<div ndProgressIndicator></div>
</div>
</li>
}
</ul>
`,
})
export class ProgressUploadExample {
protected readonly files = [
{ name: 'report.pdf', value: 100 },
{ name: 'photos.zip', value: 45 },
{ name: 'video.mp4', value: null },
];
}
Accessibility
Implements the progressbar role pattern of the WAI-ARIA Authoring Practices.
Focus
- The progress bar never receives focus and never changes focus.
Right-to-left
The indicator fills from the start side: from the right in right-to-left layouts.
API reference
Progress
[ndProgress]role progressbarA progress bar (@ndop/primitives/progress): how far a task has got,
from 0 to max, or indeterminate while value is null. Give it a
name with aria-label or aria-labelledby, and put a
ndProgressIndicator inside.
Manages aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext.
| Progress binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
[value]How far the task has got, from 0 to max; null while unknown. | number | null | null |
[max]The value of a finished task. | number | 100 |
| Data attribute | Values |
|---|---|
data-slot | progress |
data-state | — |
data-value | — |
data-max | — |
ProgressIndicator
[ndProgressIndicator]The filled part of a progress bar. It fills from the start side, so from the right in right-to-left layouts, and pulses across the whole bar while indeterminate.
| ProgressIndicator binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | progress-indicator |
data-state | — |
Agent notes
Do
- Name it with
aria-labeloraria-labelledby, such as "Upload". - Show the percentage or what's left in text next to it, too.
- Use a Spinner instead when there's no measure of progress at all.
Don't
- Don't use it to show a static quantity, such as storage used; that's a meter.
- Don't move it backwards.