apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Progress
Variant↳ other

Feedback

Progress

Progress indicators in two shapes —

Overview

<Progress /> is the linear bar (determinate + indeterminate). <CircularProgress /> is the SVG ring variant. Both share the same prop vocabulary (value, min, max, variant, size, color, indeterminate, showLabel, labelFormat, animated) and the same ARIA semantics — CSS-only animations with no external motion library.

Overview — indeterminate, 35%, and 80% bars

Loading preview…
Overview.tsx

Variants

Linear

  • solid — neutral track + role-colored bar. Default.
  • soft — role -subtle track + role-colored bar. The bar sits inside its own tinted lane.
  • striped — solid plus a diagonal CSS-gradient overlay on the bar. Reach for it on long-running uploads / installs to communicate "active work".

Circular

  • solid — bg-subtle track + role-colored arc. Default.
  • soft — role -subtle track + role-colored arc.

(No striped for circular — SVG strokes can't carry a diagonal pattern without a <pattern> def.)

Sizes

TokenLinear heightCircular diameterDefault thickness
smh-1 (4px)24px3px
mdh-2 (8px)40px4px
lgh-3 (12px)56px5px

<CircularProgress size={number} /> sets the diameter directly; thickness defaults to Math.max(2, size / 10) and can still be overridden via thickness.

Anatomy

Linear

text
[ track | bar { stripes? | label? } ]
[ track | bar { stripes? | label? } ]

Circular

text
[ root
   ↳ svg
       ↳ <circle/> (track)
       ↳ <circle/> (arc — strokeDasharray drives the visible length)
   ↳ label? ]
[ root
   ↳ svg
       ↳ <circle/> (track)
       ↳ <circle/> (arc — strokeDasharray drives the visible length)
   ↳ label? ]

Examples

Basic

Loading preview…
Basic.tsx

BasicCircular

Loading preview…
BasicCircular.tsx

Variants

Loading preview…
Variants.tsx

Sizes

Loading preview…
Sizes.tsx

Colors

Loading preview…
Colors.tsx

Indeterminate

Loading preview…
Indeterminate.tsx

WithLabel

Loading preview…
WithLabel.tsx

CustomFormat

Loading preview…
CustomFormat.tsx

Rounded

Loading preview…
Rounded.tsx

ThicknessCircular

Loading preview…
ThicknessCircular.tsx

CustomSize

Loading preview…
CustomSize.tsx

Animated

Loading preview…
Animated.tsx

Props

PropTypeDefaultDescription
animatedbooleantrueAnimate the bar's width transition when `value` changes. Honors `prefers-reduced-motion` (the Tailwind preset wires that for the indeterminate animation).
colorResponsiveValue<ProgressColor>'primary'Palette role driving the bar fill.
indeterminateboolean—When `true`, the bar paints a CSS-driven sweeping animation and `aria-valuenow` is omitted — the correct ARIA semantics for "we don't know how far along we are" progress.
labelFormatProgressLabelFormatter—Custom formatter. Falls back to `Math.round(percent) + '%'` when omitted.
maxnumber100Range maximum.
minnumber0Range minimum.
roundedResponsiveValue<ProgressRounded>'full'Corner-radius family.
showLabelbooleanfalseRender the percentage (or `labelFormat` output) inside the track. The label is `aria-hidden` because the percentage is already exposed via `aria-valuenow` / `aria-valuetext`.
sizeResponsiveValue<ProgressSize>'md'Track height / label size.
stripedbooleanfalseDiagonal stripes overlay. Setting this to `true` is equivalent to `variant='striped'`; both paths land on the same compound rule.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
valuenumber0Current value. Clamped into `[min, max]` before being painted. Ignored when `indeterminate` is `true`.
variantResponsiveValue<ProgressVariant>'solid'Stylistic family.

Accessibility

  • Both surfaces render role="progressbar" on the root element.
  • aria-valuemin, aria-valuemax, aria-valuenow are emitted when determinate.
  • aria-valuetext is always present — defaults to the labelFormat output when provided, otherwise the rounded percentage. Indeterminate emits aria-valuetext="Loading" and omits aria-valuenow (the correct ARIA for "we don't know how far along we are").
  • The visible inline / centered label is aria-hidden because the same value is already exposed via aria-valuenow / aria-valuetext.
  • The inner <svg> of <CircularProgress /> is aria-hidden — the wrapper carries the accessible name.
  • Pass aria-label (or use aria-labelledby to point at a heading) so SR users hear what is progressing. axe-core: zero violations across all 21 + 14 = 35 variant×color×size cells.

Animation / Reduced motion

  • Determinate: width (linear) / stroke-dashoffset (circular) transition over duration-normal with ease-emphasized. Pass animated={false} to opt out per-instance — the bar/arc snaps. The motion-reduce: Tailwind variant also halts the transition for users with prefers-reduced-motion: reduce.
  • Indeterminate (linear): progress-indeterminate keyframe slides the 1/3-width bar across the track on a 1.4s loop.
  • Indeterminate (circular): circular-indeterminate-spin rotates the whole SVG; in parallel circular-indeterminate-dash modulates stroke-dashoffset so the arc grows then shrinks. Both loops are 1.4s.
  • All four keyframes live in packages/theme/src/tailwind-preset.ts.
  • Reduced-motion fallback: indeterminate animations halt; the static state drops to 60% opacity so the surface still reads as "in progress" without motion.

Theming

Every visual decision is a palette token. Pull the <ThemeProvider> knobs to remix the whole library, or scope overrides to Progress / CircularProgress specifically:

tsx
<ThemeProvider
  theme={defineTheme({
    components: {
      Progress: {
        defaultProps: { variant: 'soft', rounded: 'md' },
        styleOverrides: {
          track: '',
          bar: 'opacity-90',
          label: 'tracking-wide',
        },
      },
      CircularProgress: {
        defaultProps: { thickness: 5 },
        styleOverrides: {
          root: '',
          svg: '',
          track: 'opacity-30',
          arc: '',
          label: 'tabular-nums',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>
<ThemeProvider
  theme={defineTheme({
    components: {
      Progress: {
        defaultProps: { variant: 'soft', rounded: 'md' },
        styleOverrides: {
          track: '',
          bar: 'opacity-90',
          label: 'tracking-wide',
        },
      },
      CircularProgress: {
        defaultProps: { thickness: 5 },
        styleOverrides: {
          root: '',
          svg: '',
          track: 'opacity-30',
          arc: '',
          label: 'tabular-nums',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>

Instance-level overrides win via tailwind-merge:

tsx
<Progress className="rounded-none" style={{ minWidth: 240 }} value={66} />
<CircularProgress sx={{ color: 'success.main' }} value={66} />
<Progress className="rounded-none" style={{ minWidth: 240 }} value={66} />
<CircularProgress sx={{ color: 'success.main' }} value={66} />

Do / Don't

  • Do prefer indeterminate whenever you can't measure the operation. A bar pegged at "5%" for two minutes is worse than a sweeping indeterminate bar.
  • Do pair showLabel with labelFormat for domain-specific units ("2.4 GB / 4 GB", "step 3 of 5") — the percent is rarely the most useful number on its own.
  • Don't stack more than one indeterminate spinner on a single screen — the motion competes. A single page-level spinner reads as authoritative; many small ones read as broken.
  • Don't flip animated={false} globally in a theme override unless you've also disabled the indeterminate keyframes — the result is a static bar that lies about its state.