GrProgressBar

  • core
  • feedback
  • API · 9 props

Shows the progress of a task or a loading process.

import { GrProgressBar } from '@feugene/granularity/components/GrProgressBar'

Overview

When to take it

  • the share of what is done is known — an import, a file upload, a step of a wizard: the bar answers “how much is left”;
  • the end is unknown — indeterminate shows that the process is running without promising a deadline;
  • more has been loaded than played — buffer draws a second layer: video buffering, prefetching;
  • the value has to be shown in words — showValue with formatValue instead of a separate caption.

When to take something else

NeedTake
There is no room across the widthGrProgressCircle
The share is unknown and the content is already thereGrLoading
There is no content yetGrSkeleton
The progress of a file uploadGrFileUpload
Show a share of a whole as dataGrChartPie

Variants every tone and view, rendered by the real component

tone

  • primary
  • neutral
  • success
  • warning
  • danger
  • info
  • slate
  • azure

Sizes

  • xs
  • sm
  • md
  • lg

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 5 each with a live demo and its code

  • Progress in the reader’s units

    The import counts rows, so the label does too: format-value turns the share into «1,140 of 3,000 rows» and hands the same text to aria-valuetext. aria-label names the process — without it the reader hears a number with no subject. At the end the tone becomes the verdict.

    Importing customerscustomers-2026-10.csv · matching by tax ID
    1,140 of 3,000 rows
  • Tone as a verdict

    The same bar reads differently by tone: primary on track, warning at risk, danger off track, success done — and the badge next to it says the same. Every bar shows its value, so the label keeps one width and the tracks end on one line.

    Q4 goals
    Move 40 customers to annual billingSales
    On track
    78%
    Cut invoice disputes below 2 %Finance
    At risk
    52%
    Ship the invoice editorProduct
    Done
    100%
    Answer support within 4 hoursSupport
    Off track
    31%
  • A total and its parts

    size sets the weight: lg for the total, xs for the parts under it. Inside a card the track’s own border is a second frame, so borderless removes it. In a stack of bars the values go to their own column — labels of different length would give every track a different width.

    StorageBusiness plan · deleted files are kept for 30 days
    38.2 of 50 GB
    Invoice PDFs
    21.4 GB
    Attachments
    11.2 GB
    Backups
    5.6 GB
  • A line under the header while the size is unknown

    The report does not know how long it will take, and indeterminate promises nothing: the bar runs and no value is announced. A thin xs line without a border sits on the header edge in a slot that keeps its height when idle, so the report does not jump. Under reduced motion the line becomes a still neutral fill.

    Revenue by channelUpdated 10:42
    Web store€30,060.00
    Mobile app€17,320.00
    Resellers€7,210.00

API generated from the package · cannot drift

Props

PropTypedefaultDescription
tone
"primary" | "neutral" | "success" | "warning" | "danger" | ""primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined
"primary"The colour tone of the fill.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe thickness of the track.
ariaLabelstring | undefinedundefinedThe label for a screen reader (mandatory if there is no visible heading beside it).
indeterminateboolean | undefinedfalseThe progress is unknown: the bar runs, and no value is announced outside.
borderlessboolean | undefinedundefinedRemove the border of the track: inside a card a second border is only noise.
buffernumber | undefinedundefinedLoaded ahead: a layer behind the fill, `0..100`. Unset — there is no layer.
showValueboolean | undefinedfalseShow the value as a label to the right of the track.
formatValue((value: number) => string) | undefinedundefinedA format of your own for the value. It governs both the label and `aria-valuetext`.
valuerequirednumber—The current value `0..100`; values out of range are clamped, and a non-numeric one becomes `0`.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrProgressBarProps, GrProgressBarConfigurableProps, GrProgressBarTone } from '@feugene/granularity/components/GrProgressBar'

Filedist/types/components/GrProgressBar/GrProgressBar.vue.d.ts

GrProgressBarPropsinterface
interface GrProgressBarProps {
    value: number;
    buffer?: number;
    indeterminate?: boolean;
    showValue?: boolean;
    formatValue?: (value: number) => string;

Filedist/types/components/GrProgressBar/config.d.ts

grProgressBarConfigconst
const grProgressBarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrProgressBar">;

Filedist/types/components/GrProgressBar/safelist.d.ts

grProgressBarSafelistconst
const grProgressBarSafelist: string[];

Filedist/types/components/GrProgressBar/defaults.d.ts

GrProgressBarConfigurablePropsinterface
interface GrProgressBarConfigurableProps {
    size: GrComponentSize;
    borderless: boolean;
}

Filedist/types/components/GrProgressBar/grStyle.d.ts

GrProgressBarTonetype
type GrProgressBarTone = GrTone;
GrProgressBarSizetype
type GrProgressBarSize = GrComponentSize;

Usage from the library prose

When the progress is unknown
vue
<GrProgressBar :value="percent" :indeterminate="!sizeKnown" aria-label="Import" />

indeterminate beats value: the bar runs, and aria-valuenow and aria-valuetext are not set at all — by the specification that is exactly the sign of indeterminacy, and a separate aria-busy is not needed. The label of the value is not rendered in this mode: there is nothing to show.

This is the most frequent progress scenario — the request has left and the server did not report the size of the answer. GrFileUpload switches the mode on itself when the XHR has no lengthComputable.

Under prefers-reduced-motion: reduce the bar does not freeze but becomes a neutral fill across the full width (--gr-progress-indeterminate-bg). A frozen frame of a running bar would lie at the left edge and read as “40% progress”, that is, it would lie about the state; a fill across the full width says “work is going on, the value is unknown” and pretends to be neither zero nor completion. The period of a run is --gr-progress-indeterminate-duration. The general contract of movement — motion.md.

The label of the value
vue
<GrProgressBar :value="percent" show-value aria-label="Loading" />

<GrProgressBar :value="percent" :format-value="v => `${gb(v)} of 32 GB`" show-value />

showValue prints whole percentages to the right of the track. The label is of a fixed width and uses tabular figures: the move from 9% to 10% must not jerk the track.

formatValue governs both the label and aria-valuetext — “184 of 512 MB” instead of a bare “36”. Without a format of your own aria-valuetext is not set: a screen reader already reads “36” with aria-valuemax="100" as a percentage, and there is no point duplicating that with text. The format works without showValue too — then it remains for the screen reader alone.

The buffer
vue
<GrProgressBar :value="played" :buffer="buffered" aria-label="Playback" />

A second layer behind the fill: played against buffered, filled against confirmed by the server. It is clamped by the same rules as value (0..100, NaN → 0) and is not obliged to be larger than the value — the layer will simply turn out shorter than the fill.

The buffer does not inherit tone and is coloured with a single --gr-progress-buffer-bg: not all eight tones have -light roles, and a neutral layer between the track and the fill reads well with any of them.

A value out of range

value and buffer are clamped to 0..100, and any non-numeric value turns into 0. A bar given -5 or 140 does not break the layout and does not give away an invalid aria-valuenow — that is a deliberate boundary rather than a side effect.

A mandatory value that did not arrive falls here as well: the bar is drawn empty, aria-valuenow="0", and in dev mode the component prints a warning. Staying silent here is not allowed — a screen reader reads aria-valuenow="NaN", and that is an axe violation.

Styling

size is xs…lg and is read from GrConfigProvider; for a linear bar that is the thickness of the track and the type size of the label, while the width is set by the container. The tone of the fill is tone from the palette of the package.

borderless removes the border of the track and is likewise read from GrConfigProvider. The border is needed on a bare background, where the --gr-muted track is barely distinguishable from the page; inside a card it becomes a second border next to its own.

Pointed customisation is done with the --gr-progress-bg variables (and one per tone), --gr-progress-buffer-bg, --gr-progress-indeterminate-bg and --gr-progress-indeterminate-duration. The full list — tokens.md.

The accessible name

role="progressbar" stands on the track, and ariaLabel is the only way to give the bar a name: a neighbouring heading does not count as a name. Inside GrFileUpload the label is substituted by the uploader itself.