ProgressBar
A labelled bar for an operation whose progress you can measure, with a status or error line under it. It is three stacked lines — label, bar, status — and it is always as wide as its container.
Use this when / not when
- Use when you can say how far along the work is: an upload, a conversion, a bulk action.
- Use
isInfiniteProgresswhen you cannot, and the operation still has its own place on the screen — but read what it does to the announced value below. - Not for a page-wide wait with nothing to show — that is
TopLoaderServiceat the top of the window orAppLoaderover it. - Not for dimming a form while it saves —
LoaderWrapper. - It is not a slider. Nothing here is interactive, and there is no way to click a position.
Import
import { ProgressBar } from "@onlyoffice/apps-ui-kit/components/progress-bar";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
Needs ThemeProvider above it in the tree. The track and fill colours are declared only under the
light and dark classes it puts on <body>, and a colour that does not resolve leaves the
element with no background at all — an invisible bar, not a default-coloured one. Setting
--progress-bar-track and --progress-bar-fill yourself is the other way out.
Stories
Default
Use it for an operation whose progress you can measure: the label names the operation and the fill shows how far it has got (percent, label). Change any other prop live in the Controls panel below.
With Status
Add a status line when the reader needs more than the fill tells them — how many items are done, what is being processed now (status).
With Error
When the operation fails, the message takes the place of the status line in the error colour, and the bar stays where it stopped (error).
Infinite Progress
Use it when the operation cannot report how far it has got: a short strip slides across the track until the bar is removed (isInfiniteProgress).
Complete
The finished state: the track is filled completely and the status line confirms the result (percent={100}).
Preparation Portal
Use PreparationPortalProgress for a full-page wait where the number itself matters: a taller bar with the percentage printed in its middle and a centred caption below (percent, text). The percentage turns from dark to light once the fill passes 50%; move percent in the Controls panel below to see it.
Right To Left
The bar under a right-to-left interface: the label and status line align to the right, the fill grows from the right edge, and the infinite strip in the second bar slides from right to left. The direction comes from the theme's interfaceDirection (the Direction toolbar); the wrapper also carries dir="rtl" for the rules that read the DOM direction.
Css Customization
The variables are listed under CSS variables on this page; the example sets every one of them on one wrapper.
The first bar shows the track, fill, size, radius, margin and status colour; the second sets error to show --progress-bar-error-text, since an error takes the place of the status line.

Minimal example
import { useEffect, useState } from "react";
import { ProgressBar } from "@onlyoffice/apps-ui-kit/components/progress-bar";
export function Upload() {
const [percent, setPercent] = useState(0);
useEffect(() => {
const id = setInterval(
() => setPercent((value) => (value >= 100 ? 100 : value + 10)),
300,
);
return () => clearInterval(id);
}, []);
return <ProgressBar percent={percent} label="Uploading Report.docx" />;
}
Props
Recipes
Error
error takes the same line as status and wins over it. The bar itself does not change — it stays
where it stopped, in the ordinary fill colour — so say in the text what happened.
import { ProgressBar } from "@onlyoffice/apps-ui-kit/components/progress-bar";
export function FailedUpload() {
return (
<ProgressBar
percent={30}
label="Report.docx"
status="Uploading"
error="The connection was lost"
/>
);
}
A wait with no measurable end
isInfiniteProgress replaces the filled portion with a strip that slides across the track for
ever. Give it a label: the bar carries no other clue about what is happening.
import { ProgressBar } from "@onlyoffice/apps-ui-kit/components/progress-bar";
export function Converting() {
return <ProgressBar percent={0} label="Converting" isInfiniteProgress />;
}
A thicker, differently coloured bar
Six custom properties cover the shape and the colours, and they override the theme rather than supplement it, so a bar styled this way looks the same in both themes.
import type { CSSProperties } from "react";
import { ProgressBar } from "@onlyoffice/apps-ui-kit/components/progress-bar";
export function ThickBar() {
return (
<div
style={
{
"--progress-bar-size": "8px",
"--progress-bar-radius": "8px",
"--progress-bar-track": "#e9d5ff",
"--progress-bar-fill": "#7c3aed",
} as CSSProperties
}
>
<ProgressBar percent={65} label="Indexing" />
</div>
);
}
Behaviour the types don't state
- Without a theme provider the bar is invisible. Both colours resolve through variables that
exist only under the provider's
lightanddarkclasses, and a failingvar()throws away the wholebackground-colordeclaration — the track and the fill both fall back to transparent. - The fill is a different colour in each theme, and not a matching one: the kit's blue under
the light theme, the warning orange under the dark one. Set
--progress-bar-fillif a progress bar should not look like a warning at night. - It brings its own spacing. 10px above the label and 8px below the bar, and the label's line is
rendered even when there is no
label— so an unlabelled bar still starts 10px down. The bottom margin is--progress-bar-bottom-margin; the top one is not adjustable. percentis clamped above but not below. Over 100 becomes 100; a negative number makes the width declaration invalid, so the bar reads as empty whilearia-valuenowreports the negative value.classNameandstyleland in different places:classNameon the bar,styleon the container around all three lines.- Extra DOM attributes do not compile. The props type is closed — no
id, nodata-*, no event handlers — even though the component spreads a rest object onto the bar. - The stylesheet's
--progress-percentdoes nothing. The width of the fill is written as an inline style, which beats the rule that reads that property. - It mirrors in a right-to-left interface. The fill grows from the right edge, and the sliding
strip of the infinite mode travels right to left. Both follow the theme provider — its
data-diron<html>and the direction it gives<body>— so nodirattribute is needed. - The fill transitions over 0.6s, so a bar driven in large steps keeps moving after the number has changed.
Sub-components
PreparationPortalProgress
A second, unrelated bar: 24px tall with the percentage written inside it, used on the portal's
"preparing your workspace" screen. It is exported from the same folder and shares none of
ProgressBar's props or custom properties.
- It announces nothing by default. The
roleandaria-*props exist because the component sets none of them; pass them or the bar is a pair of anonymous<div>s. percentis not clamped here. Over 100 the filled line simply overflows its track.- The number inside the bar is absolutely positioned near the middle and flips from dark to light
once
percentpasses 50, so it stays readable over the fill. - The bar carries a 16px bottom margin of its own, and the text under it is centred.
CSS variables
--progress-bar-error-text shows only while error is set, since the error line takes the place
of the status line. PreparationPortalProgress reads none of these.
Accessibility
- The bar is a
role="progressbar"witharia-valuemin,aria-valuemaxandaria-valuenow, and its accessible name is thelabel— without a label it has none, and a screen reader announces a bare percentage. - The infinite mode is not marked indeterminate.
aria-valuenowkeeps reporting whateverpercentis, usually 0, while the strip slides; a screen reader is told the work has not started. Either keeppercentmeaningful or hide the bar from assistive technology and announce the state in text. - The status and error lines are ordinary text, not a live region: a change is not announced. Put
them in an
aria-live="polite"container of your own when the outcome matters. - The error state is carried by the colour of one line and by its wording — nothing sets
aria-invalidor a role — so the wording has to be enough on its own.
Test ids
| Element | data-testid |
|---|---|
| The bar | progress-bar |
| The filled portion | progress-bar-percent |
| The sliding strip (infinite) | progress-bar-animation |
PreparationPortalProgress | preparation-portal-progress |
Only the last is settable, through that component's data-testid prop. The bar also carries
data-progress, and data-status or data-error when either is set.
Related
LoaderWrapper— dims the area the operation belongs to.TopLoaderService— the page-wide bar for a wait with no number.Loader— a spinner when there is nothing to measure.