component

Progress

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.

Variants

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.

Linear

Linear Indeterminate

Circular

Circular Indeterminate

---
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>

Feedback Colors

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.

Neutral

Primary

Success

Warning

Danger

---
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>

Sizes

Three sizes — sm, md (default), lg — scale the bar height and ring diameter proportionally. The label slot font size scales with the chosen size.

Linear Sizes

Circular Sizes

---
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>

Labels

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.

Default percentage

65%
65%

Step count

Step 3 of 5
3/5

File size

42 MB of 100 MB
42 MB
---
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>

Custom Styling

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.

Custom Max and Label

3 of 10 steps

Custom Track Styling

Accessible Label via Props

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>

Props

props · 11 total
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.