Skip to content
v2.5.0
English
Русский
Documentation

Progress

Progress communicates completion of an ongoing task or operation.
Uploading report.pdf64%
progress.tsx · 9 lines
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
Tested in
18 tests

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

  • ProgressProps
  • ProgressTrackProps
  • ProgressSize
  • ProgressVariant

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.

minimal-composition.tsx · 9 lines
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 surface
  • ProgressTrack: visible fill track

Optional Parts

  • No extra public parts beyond root and track

Composition Order

  1. Progress
  2. ProgressTrack

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, Meter is 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.

upload-status-row.tsx · 27 lines
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.

parent-owned-upload-progress.tsx · 58 lines
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.

form-attachment-upload.tsx · 62 lines
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.

progress-inside-a-dialog.tsx · 82 lines
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.

failed-progress-flow-with-stopped-completion.tsx · 70 lines
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.

indeterminate-progress.tsx · 72 lines
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

  1. Progress
  2. ProgressTrack

Valid Composition Patterns

minimal-composition.tsx · 9 lines
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.