ProgressBar
A progress bar shows either determinate or indeterminate progress of an operation over time.
Usage
import { ProgressBar, Label } from '@lenso/ui';"use client";
import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ progress: { width: 256 } });
export function Basic() { return ( <ProgressBar aria-label="Loading" xstyle={styles.progress} value={60}> <ProgressBar.Label>Loading</ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
import { ProgressBar, Label } from '@lenso/ui';
export default () => ( <ProgressBar value={60}> <Label>Loading</Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar>);Examples
Sizes
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0).import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ column: { display: "flex", width: 256, flexDirection: "column", gap: 24 },});export function Sizes() { return ( <div {...stylex.props(styles.column)}> <ProgressBar aria-label="Small" size="sm" value={40}> <ProgressBar.Label>Small</ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> <ProgressBar aria-label="Medium" size="md" value={60}> <ProgressBar.Label>Medium</ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> <ProgressBar aria-label="Large" size="lg" value={80}> <ProgressBar.Label>Large</ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> </div> );}export default Sizes;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Colors
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0).import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ column: { display: "flex", width: 256, flexDirection: "column", gap: 24 },});export function Colors() { return ( <div {...stylex.props(styles.column)}> {(["default", "accent", "success", "warning", "danger"] as const).map((color) => ( <ProgressBar key={color} color={color} value={50}> <ProgressBar.Label> {color[0]?.toUpperCase()} {color.slice(1)} </ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> ))} </div> );}export default Colors;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Without Label
When no visible label is needed, use aria-label for accessibility.
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0).import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ progress: { width: 256 } });export function WithoutLabel() { return ( <ProgressBar aria-label="Loading progress" xstyle={styles.progress} value={45}> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> );}export default WithoutLabel;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Indeterminate
Use isIndeterminate when progress cannot be determined.
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0); null is Base UI's indeterminate value.import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ progress: { width: 256 } });export function Indeterminate() { return ( <ProgressBar aria-label="Loading" xstyle={styles.progress} value={null}> <ProgressBar.Label>Loading...</ProgressBar.Label> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> );}export default Indeterminate;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Custom Value Scale
Use minValue, maxValue, and formatOptions to customize the value range and display format.
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0); native Base UI range and selection contracts.import { NumberField, ProgressBar, Select, Separator } from "@lenso/ui";import { useId, useState } from "react";import * as stylex from "@stylexjs/stylex";const formatStyleOptions = [ { label: "Currency", value: "currency" }, { label: "Percent", value: "percent" }, { label: "Decimal", value: "decimal" }, { label: "Unit", value: "unit" },];const formatOptionsMap: Record<string, Intl.NumberFormatOptions> = { currency: { currency: "USD", style: "currency" }, decimal: { style: "decimal" }, percent: { style: "percent" }, unit: { style: "unit", unit: "mile" },};const styles = stylex.create({ root: { display: "flex", width: "100%", flexDirection: { default: "column", "@media (min-width: 768px)": "row" }, alignItems: { default: "stretch", "@media (min-width: 768px)": "center" }, gap: { default: 24, "@media (min-width: 768px)": 40 }, }, preview: { display: "flex", width: "100%", maxWidth: 448, flex: 1, justifyContent: "center" }, progress: { width: "100%", maxWidth: 208 }, horizontal: { display: { default: "block", "@media (min-width: 768px)": "none" } }, vertical: { display: { default: "none", "@media (min-width: 768px)": "block" } }, options: { display: "flex", maxWidth: 208, flexDirection: "column", gap: 12 }, caption: { fontSize: 12, lineHeight: "16px", fontWeight: 500, color: "var(--muted)" }, field: { display: "flex", flexDirection: "column", gap: 4 }, label: { fontSize: 14, lineHeight: "20px", fontWeight: 500 },});export function CustomValue() { const valueId = useId(); const minId = useId(); const maxId = useId(); const [value, setValue] = useState(750); const [minValue, setMinValue] = useState(0); const [maxValue, setMaxValue] = useState(1000); const [format, setFormat] = useState("percent"); return ( <div {...stylex.props(styles.root)}> <div {...stylex.props(styles.preview)}> <ProgressBar aria-label="Revenue" xstyle={styles.progress} format={formatOptionsMap[format]} max={maxValue} min={minValue} value={value} > <ProgressBar.Label>Progress</ProgressBar.Label> <ProgressBar.Output /> <ProgressBar.Track> <ProgressBar.Fill /> </ProgressBar.Track> </ProgressBar> </div> <Separator xstyle={styles.horizontal} /> <Separator xstyle={styles.vertical} orientation="vertical" /> <div {...stylex.props(styles.options)}> <p {...stylex.props(styles.caption)}>Options</p> <NumberField id={valueId} min={minValue} max={maxValue} value={value} variant="secondary" onValueChange={(next) => { if (next !== null) setValue(next); }} > <label htmlFor={valueId} {...stylex.props(styles.label)}> Value </label> <NumberField.Group> <NumberField.DecrementButton /> <NumberField.Input /> <NumberField.IncrementButton /> </NumberField.Group> </NumberField> <NumberField id={minId} min={0} max={maxValue - 1} value={minValue} variant="secondary" onValueChange={(next) => { if (next === null) return; setMinValue(next); setValue((current) => Math.max(next, current)); }} > <label htmlFor={minId} {...stylex.props(styles.label)}> Min Value </label> <NumberField.Group> <NumberField.DecrementButton /> <NumberField.Input /> <NumberField.IncrementButton /> </NumberField.Group> </NumberField> <NumberField id={maxId} min={minValue + 1} max={2000} value={maxValue} variant="secondary" onValueChange={(next) => { if (next === null) return; setMaxValue(next); setValue((current) => Math.min(next, current)); }} > <label htmlFor={maxId} {...stylex.props(styles.label)}> Max Value </label> <NumberField.Group> <NumberField.DecrementButton /> <NumberField.Input /> <NumberField.IncrementButton /> </NumberField.Group> </NumberField> <div {...stylex.props(styles.field)}> <Select variant="secondary" value={format} items={formatStyleOptions} onValueChange={(next) => { if (next !== null) setFormat(next); }} > <Select.Label>Format</Select.Label> <Select.Trigger> <Select.Value /> <Select.Indicator /> </Select.Trigger> <Select.Portal> <Select.Positioner> <Select.Popover> <Select.List> {formatStyleOptions.map((option) => ( <Select.Item key={option.value} value={option.value}> <Select.ItemText>{option.label}</Select.ItemText> <Select.ItemIndicator /> </Select.Item> ))} </Select.List> </Select.Popover> </Select.Positioner> </Select.Portal> </Select> </div> </div> </div> );}export default CustomValue;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Customization
Tailwind CSS
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0).import { ProgressBar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ progress: { width: 256 }, label: { fontWeight: 500, color: "var(--foreground)" }, output: { fontSize: 12, lineHeight: "16px", color: "var(--muted)", fontVariantNumeric: "tabular-nums", }, track: { borderRadius: 9999, backgroundColor: "var(--default)" }, fill: { borderRadius: 9999, backgroundColor: "var(--accent)" },});export function CustomStyles() { return ( <ProgressBar aria-label="Upload progress" xstyle={styles.progress} value={45}> <ProgressBar.Label xstyle={styles.label}>Uploading resume.pdf</ProgressBar.Label> <ProgressBar.Output xstyle={styles.output} /> <ProgressBar.Track xstyle={styles.track}> <ProgressBar.Fill xstyle={styles.fill} /> </ProgressBar.Track> </ProgressBar> );}export default CustomStyles;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
To customize the ProgressBar component classes, you can use the @layer components directive.
Learn more.
@layer components { .progress-bar { @apply w-full gap-2; }
.progress-bar__track { @apply h-3 rounded-full; }
.progress-bar__fill { @apply rounded-full; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The ProgressBar component uses these CSS classes (View source styles):
Base & Element Classes [!toc]
.progress-bar- Base container (grid layout).progress-bar__output- Value text display.progress-bar__track- Track background.progress-bar__fill- Filled portion of the track
Size Classes [!toc]
.progress-bar--sm- Small size variant (thinner track).progress-bar--md- Medium size variant (default).progress-bar--lg- Large size variant (thicker track)
Color Classes [!toc]
.progress-bar--default- Default color variant.progress-bar--accent- Accent color variant.progress-bar--success- Success color variant.progress-bar--warning- Warning color variant.progress-bar--danger- Danger color variant
API Reference
ProgressBar
Inherits from React Aria ProgressBar.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | 0 | The current value |
minValue | number | 0 | The minimum value |
maxValue | number | 100 | The maximum value |
isIndeterminate | boolean | false | Whether progress is indeterminate |
size | "sm" | "md" | "lg" | "md" | Size of the progress track |
color | "default" | "accent" | "success" | "warning" | "danger" | "accent" | Color of the fill bar |
formatOptions | Intl.NumberFormatOptions | {style: 'percent'} | Number format for the value display |
valueLabel | ReactNode | - | Custom value label content |
children | ReactNode | (values: ProgressBarRenderProps) => ReactNode | - | Content or render prop |
Render Props
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
|---|---|---|
percentage | number | The percentage of the progress (0-100) |
valueText | string | The formatted value text |
isIndeterminate | boolean | Whether progress is indeterminate |