Progress

A progress bar shows how far along a task is, such as an upload, an import or a setup step.

A progress bar shows how much of a task is done. Pass value when you know it, and leave it out when you don't. For a short wait with nothing to measure, a Spinner takes less room. To let people pick a value on a track, use a Slider.

Playground

Uploading report.pdf60%

Usage

import { Flex, Progress, Text } from '@kushagradhawan/kookie-ui';
 
export function UploadProgress({ fileName, percent }: { fileName: string; percent: number }) {
  return (
    <Flex direction="column" gap="2">
      <Flex justify="between" gap="3">
        <Text id="upload-label" size="2">
          Uploading {fileName}
        </Text>
        <Text size="2" emphasis="medium">
          {percent}%
        </Text>
      </Flex>
      <Progress value={percent} aria-labelledby="upload-label" />
    </Flex>
  );
}

Best practices

Show a value whenever you have one. A bar that moves with real numbers tells people how long to wait. A bar without a value only says that something is happening.

Say what is in progress. Put a label above the bar, such as "Uploading report.pdf", and connect it with aria-labelledby. A bar on its own doesn't say what it measures.

Show the number too. Put the percentage or "3 of 12 files" next to the label. The bar is hard to read precisely, and the number gives people something to compare.

Never move it backwards. If the total grows during the task, recalculate in a way that keeps the bar still or moving forward. A bar that jumps back makes people think the task restarted.

Use a Spinner for waits under a few seconds. A bar that appears and fills at once is noise. Use a Spinner, or the loading prop on a Button when the wait follows a press.

Value

value is a number from 0 to max, which is 100 unless you set it. With max={12}, a value of 3 fills a quarter of the bar. A value outside that range is ignored and the bar treats it as unknown.

Without value, the bar is indeterminate: it fills from the start to the end over duration, then starts again. duration is 5 seconds unless you set it, such as duration="2s" or duration="800ms". Set it close to how long the task usually takes, so the bar feels like it matches the work. duration applies only while the bar is indeterminate: value is undefined, null, or outside 0 to max.

Variant

VariantLook
surfaceA light track with a border. This is the default
softA filled track with no border
outlineA border with no fill

The bar is 6 pixels tall and grows to fill its row in a Flex. The filled part uses color.

Examples

Counting steps

Set max to the number of steps, so the value reads in steps rather than percent.

<Progress value={3} max={12} aria-label="Importing contacts" getValueLabel={(value, max) => `${value} of ${max} files`} />

Unknown length

Pass null while the server hasn't told you the total yet, then pass the value once you know. The bar switches from indeterminate to filled in place.

<Progress value={total ? (done / total) * 100 : null} aria-label="Preparing export" />

Status colours

Use color to match what the bar reports, such as green once a check has passed or red for a quota that is almost full. Keep the text saying the same thing, because colour alone doesn't reach everyone.

Props

PropTypeDefaultDescription
valuenumber | nullHow much is done, from 0 to max. Leave it out for an indeterminate bar
maxnumber100The value that fills the bar
getValueLabel(value: number, max: number) => stringPercentThe text a screen reader reads for the value
duration`${number}s` | `${number}ms`'5s'How long one indeterminate fill takes. Only without value
variant'surface' | 'soft' | 'outline''surface'The look
colorAccentColorTheme accentThe colour of the filled part
radius'none' | 'small' | 'medium' | 'large' | 'full'Theme radiusThe corner

Progress also accepts every attribute of the HTML div element.

Accessibility

  • Name: Progress has role="progressbar" and no name of its own. Pass aria-labelledby pointing at the visible label, or aria-label.
  • Value: It sets aria-valuenow, aria-valuemin of 0 and aria-valuemax. A screen reader reads the value as a percentage, such as "25%", or as the text from getValueLabel.
  • Indeterminate: Without a value, it sets no aria-valuenow or aria-valuetext, so a screen reader reads the name with no amount.
  • Right to left: In a right-to-left layout the bar fills from the right.
  • Motion: With reduced motion turned on, the indeterminate bar keeps moving, three times more slowly.
  • Forced colours: In Windows high contrast mode the track has a border and the filled part uses the system highlight colour.
  • Focus: It doesn't take focus or respond to the pointer. To announce progress as it changes, update a nearby role="status" element at milestones rather than on every tick.