A stateless progress indicator that displays the completion of a task. Renders as a native <progress> element for linear bars — giving you built-in browser semantics — and as an SVG ring with role="progressbar" for the circular variant. Use it for uploads, loading states, step flows, and any operation where showing progress reduces perceived wait time.
The variant prop switches between linear (a horizontal bar) and circular (a ring). Both support determinate states (via value and max) and indeterminate states (omit value or pass indeterminate). Indeterminate linear bars animate with a sliding shimmer; indeterminate rings spin continuously.
---
import Progress from "../Progress.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<!-- Linear determinate -->
<div>
<h3>Linear</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Progress value={30} label="Task progress" />
<Progress value={65} label="Upload progress" />
<Progress value={100} label="Complete" />
</div>
</div>
<!-- Linear indeterminate -->
<div>
<h3>Linear Indeterminate</h3>
<Progress indeterminate={true} label="Loading…" />
</div>
<!-- Circular determinate -->
<div>
<h3>Circular</h3>
<div class={stack({ gap: "md", direction: "row", align: "center" })}>
<Progress variant="circular" value={30} label="30%" />
<Progress variant="circular" value={65} label="65%" />
<Progress variant="circular" value={100} label="Complete" />
</div>
</div>
<!-- Circular indeterminate -->
<div>
<h3>Circular Indeterminate</h3>
<Progress variant="circular" indeterminate={true} label="Loading…" />
</div>
</div>Use feedback to convey meaning — success for a completed upload, danger for a failed operation, warning for a degraded state, primary for general brand progress. Both linear and circular variants respond to the same feedback tokens.
---
import Progress from "../Progress.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Neutral</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={60} feedback="neutral" label="Neutral progress" />
<Progress variant="circular" value={60} feedback="neutral" label="60%" />
</div>
</div>
<div>
<h3>Primary</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={60} feedback="primary" label="Primary progress" />
<Progress variant="circular" value={60} feedback="primary" label="60%" />
</div>
</div>
<div>
<h3>Success</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={60} feedback="success" label="Success progress" />
<Progress variant="circular" value={60} feedback="success" label="60%" />
</div>
</div>
<div>
<h3>Warning</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={60} feedback="warning" label="Warning progress" />
<Progress variant="circular" value={60} feedback="warning" label="60%" />
</div>
</div>
<div>
<h3>Danger</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={60} feedback="danger" label="Danger progress" />
<Progress variant="circular" value={60} feedback="danger" label="60%" />
</div>
</div>
</div>Three sizes — sm, md (default), lg — scale the bar height and ring diameter proportionally. The label slot font size scales with the chosen size.
---
import Progress from "../Progress.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Linear Sizes</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Progress size="sm" value={50} label="Small" />
<Progress size="md" value={50} label="Medium (default)" />
<Progress size="lg" value={50} label="Large" />
</div>
</div>
<div>
<h3>Circular Sizes</h3>
<div class={stack({ gap: "md", direction: "row", align: "center" })}>
<Progress variant="circular" size="sm" value={50} label="Small" />
<Progress
variant="circular"
size="md"
value={50}
label="Medium (default)"
/>
<Progress variant="circular" size="lg" value={50} label="Large" />
</div>
</div>
</div>Use showLabel to render a visible value next to the indicator. By default it shows the percentage (65%). Pass formatValue to replace that with any string — step counts, file sizes, or any unit that fits the context.
---
import Progress from "../Progress.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Default percentage</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress value={65} showLabel label="Upload progress" />
<Progress
variant="circular"
value={65}
showLabel
label="Upload progress"
/>
</div>
</div>
<div>
<h3>Step count</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress
value={3}
max={5}
showLabel
formatValue={(v, m) => `Step ${v} of ${m}`}
label="Onboarding progress"
/>
<Progress
variant="circular"
value={3}
max={5}
showLabel
formatValue={(v, m) => `${v}/${m}`}
label="Onboarding progress"
/>
</div>
</div>
<div>
<h3>File size</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
<Progress
value={42}
max={100}
showLabel
formatValue={(v, m) => `${v} MB of ${m} MB`}
feedback="primary"
label="Download progress"
/>
<Progress
variant="circular"
value={42}
max={100}
showLabel
formatValue={(v, _m) => `${v} MB`}
feedback="primary"
label="Download progress"
/>
</div>
</div>
</div>Use formatValue to replace the default percentage label with any string (e.g. step counts). Use passThrough.root.style for Panda CSS layout overrides on the wrapper, and passThrough.root.props to forward ARIA attributes or data attributes to the outermost element.
Use passThrough.root.props to attach ARIA attributes, event handlers,
or data attributes to the outer wrapper element.
---
import Progress from "../Progress.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<!-- Custom max + label formatter -->
<div>
<h3>Custom Max and Label</h3>
<Progress
value={3}
max={10}
showLabel={true}
formatValue={(v, m) => `${v} of ${m} steps`}
label="Step progress"
feedback="primary"
/>
</div>
<!-- Custom track style via passThrough -->
<div>
<h3>Custom Track Styling</h3>
<Progress
value={70}
feedback="success"
passThrough={{
root: {
style: css.raw({
maxWidth: "400px",
}),
},
track: {
style: css.raw({
borderRadius: "none",
}),
},
}}
label="Custom radius progress"
/>
</div>
<!-- Accessible label via passThrough -->
<div>
<h3>Accessible Label via Props</h3>
<Progress
variant="circular"
value={42}
feedback="warning"
passThrough={{
root: {
props: {
"aria-describedby": "upload-status",
},
},
}}
label="File upload"
/>
<p
id="upload-status"
class={css({ fontSize: "sm", color: "fg.subtle", mt: "xs" })}
>
Use <code>passThrough.root.props</code> to attach ARIA attributes, event handlers,
or data attributes to the outer wrapper element.
</p>
</div>
</div>| prop | type | default | req | description |
|---|---|---|---|---|
| value | number | undefined | Current progress value. Omitting this (without passing indeterminate) infers an indeterminate state. | |
| max | number | 100 | Maximum value. The percentage is computed as value / max. | |
| indeterminate | boolean | false | Force indeterminate state regardless of the value prop. Useful when you explicitly want indeterminate behaviour even while a value is available. | |
| variant | "linear""circular" | "linear" | Visual shape. linear renders a horizontal bar using a native <progress> element. circular renders an SVG ring with role=progressbar. | |
| size | "sm""md""lg" | "md" | Controls bar height (linear) or ring diameter (circular), and scales the label font size. | |
| feedback | "neutral""primary""success""warning""danger" | "primary" | Semantic color role applied to the indicator. Defaults to primary. Use neutral, success, warning, or danger to convey meaning. | |
| showLabel | boolean | false | Render a visible percentage label next to the indicator. Hidden when the progress is indeterminate. | |
| formatValue | (value: number, max: number) => string | undefined | Custom label formatter. Receives the clamped value and max; return any string. Only called when showLabel is true and the state is not indeterminate. | |
| label | string | undefined | Accessible label text passed as aria-label to the native <progress> (linear) or SVG progressbar (circular). | |
| passThrough | { root?: { style?: SystemStyleObject; props?: Record<string, unknown> }; track?: { style?: SystemStyleObject; props?: Record<string, unknown> }; indicator?: { style?: SystemStyleObject; props?: Record<string, unknown> }; label?: { style?: SystemStyleObject; props?: Record<string, unknown> } } | undefined | Custom styling and props for component slots (root, track, indicator, label). style accepts any Panda CSS SystemStyleObject; props forwards arbitrary HTML or SVG attributes. | |
| ...rest | HTMLAttributes<HTMLDivElement> | - | Standard HTML div attributes forwarded to the wrapper element. |