import { Progress, ProgressTrack, Text } from '@vega-ui/react';
<div>
<div>
<Text id='preview-progress-label' size={2} fontWeight={500}>Uploading report.pdf</Text>
<Text size={2}>64%</Text>
</div>
<Progress value={64} aria-labelledby='preview-progress-label' fullWidth><ProgressTrack /></Progress>
</div>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :where()
Summary
Progress communicates completion of an ongoing task or operation. It is the temporal-feedback bar when the UI should read as work moving toward completion.
Imports
import {
Progress,
ProgressTrack,
type ProgressProps,
type ProgressSize,
type ProgressVariant,
} from '@vega-ui/react';Exported Types
ProgressPropsProgressTrackPropsProgressSizeProgressVariant
Minimal Composition
max defaults to 100. Without ProgressTrack the bar renders empty, and without aria-label or aria-labelledby the progressbar has no accessible name.
import { Progress, ProgressTrack } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<Progress value={40} aria-label='Upload progress' fullWidth>
<ProgressTrack />
</Progress>
);
}Required Parts
Progress: root progressbar surfaceProgressTrack: visible fill track
Optional Parts
- No extra public parts beyond root and track
Composition Order
ProgressProgressTrack
Variants
- Sizes:
sm,md,lg - Variants:
primary,secondary - States: determinate, indeterminate, full-width
Edge Cases
- Indeterminate and determinate states should be distinguished in surrounding UI.
- Progress values must stay within the expected range at the consumer layer.
- Use descriptive surrounding copy when users need to know what is progressing.
- If the value is actually a score, usage level, or capacity indicator,
Meteris usually the clearer abstraction.
Common Mistakes
- Using progress styling for static scores.
- Omitting text context for what is progressing.
- Feeding values that do not match the displayed range.
Examples
Basic
Basic: file upload row
A determinate progress bar labelled by the file name through aria-labelledby. valueText gives a screen reader the same “percent and megabytes” summary that is shown next to the bar.
import { Progress, ProgressTrack, Text } from '@vega-ui/react';
import { useId } from 'react';
export default function UploadStatusRow() {
const nameId = useId();
const uploadedMb = 4.2;
const totalMb = 12;
const percent = Math.round((uploadedMb / totalMb) * 100);
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 'var(--spacing-8)' }}>Controlled/Stateful
Controlled/Stateful: upload with pause and cancel
The parent owns the percentage and a running flag; an effect advances the value only while the upload is running and stops at 100. Status changes go to a role='status' line, while the percentage itself updates silently, so a screen reader is not flooded with every tick.
import { Button, Progress, ProgressTrack, Text } from '@vega-ui/react';
import { useEffect, useId, useState } from 'react';
export default function ParentOwnedUploadProgress() {
const labelId = useId();
const [percent, setPercent] = useState(0);
const [running, setRunning] = useState(false);
const done = percent === 100;
const uploading = running && !done;
useEffect(() => {Form/Integration
Form/Integration: attachment upload after submit
A real form with a required title and a required file input. The submit handler reads both with FormData and starts the upload; while it runs, a native <fieldset disabled> locks every field and the submit button, and the progress bar appears below the form.
import { Button, HelperText, Label, Progress, ProgressTrack, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useEffect, useId, useState } from 'react';
export default function FormAttachmentUpload() {
const titleId = useId();
const fileId = useId();
const progressLabelId = useId();
const [fileName, setFileName] = useState('');
const [percent, setPercent] = useState(0);
const uploading = fileName !== '' && percent < 100;
const done = fileName !== '' && percent === 100;Layout/Overlay
Layout/Overlay: export inside a dialog
The dialog owns the running operation: opening it starts the export, closing it cancels. The progress uses a count-based max (invoices, not percent) and shares the dialog title as its accessible name.
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Progress,
ProgressTrack,
Text,Error
Error: upload stops with an error
The first attempt fails at 65%: the bar stays where the upload stopped, turns red through --progress-track-color, and an error HelperText is announced and linked with aria-describedby. Retry continues from the same point and completes.
import { Button, HelperText, Progress, ProgressTrack, Text } from '@vega-ui/react';
import { CSSProperties, useEffect, useId, useState } from 'react';
type Phase = 'idle' | 'uploading' | 'failed' | 'done';
export default function FailedProgressFlowWithStoppedCompletion() {
const labelId = useId();
const errorId = useId();
const [phase, setPhase] = useState<Phase>('idle');
const [percent, setPercent] = useState(0);
const [attempt, setAttempt] = useState(0);
Edge
Edge: unknown duration, then a known total
While the file is being read, the total is unknown, so the bar is indeterminate and has no value. As soon as the row count is known, the same bar switches to a determinate value with max equal to that count.
import { Button, Progress, ProgressTrack, Text } from '@vega-ui/react';
import { useEffect, useId, useState } from 'react';
type Phase = 'idle' | 'preparing' | 'importing' | 'done';
export default function IndeterminateProgress() {
const labelId = useId();
const [phase, setPhase] = useState<Phase>('idle');
const [total, setTotal] = useState(0);
const [imported, setImported] = useState(0);
useEffect(() => {Anatomy
Overview
Progress is a compound component because the root progressbar role and the visible fill track share one value model and state contract.
Required Parts
Progress
Required. Owns range values, variant, size, and accessibility attributes.
ProgressTrack
Required. Renders the visible fill amount or indeterminate animation.
Optional Parts
Progress does not expose additional public parts beyond the root and track.
Composition Order
ProgressProgressTrack
Valid Composition Patterns
import { Progress, ProgressTrack } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<Progress value={40} aria-label='Upload progress' fullWidth>
<ProgressTrack />
</Progress>
);
}Invalid Composition Patterns
Progress without descriptive surrounding context
Users may see the bar but not know what is advancing.
Static score represented as progress
If the value is descriptive rather than temporal, Meter is usually the better pattern.