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

---
// GENERATED from packages/demos/progress/variants.demo.tsx — edit the source, not this file.
import Progress from "@pindoba/astro-progress";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <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>

  <div>
    <h3>Linear Indeterminate</h3>
    <Progress indeterminate label="Loading…" />
  </div>

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

  <div>
    <h3>Circular Indeterminate</h3>
    <Progress variant="circular" indeterminate label="Loading…" />
  </div>
</div>

Tone Colors

Use tone 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 tone tokens.

Neutral

Primary

Success

Warning

Danger

---
// GENERATED from packages/demos/progress/tone.demo.tsx — edit the source, not this file.
import Progress from "@pindoba/astro-progress";
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} tone="neutral" label="Neutral progress" />
      <Progress variant="circular" value={60} tone="neutral" label="60%" />
    </div>
  </div>

  <div>
    <h3>Primary</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      <Progress value={60} tone="primary" label="Primary progress" />
      <Progress variant="circular" value={60} tone="primary" label="60%" />
    </div>
  </div>

  <div>
    <h3>Success</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      <Progress value={60} tone="success" label="Success progress" />
      <Progress variant="circular" value={60} tone="success" label="60%" />
    </div>
  </div>

  <div>
    <h3>Warning</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      <Progress value={60} tone="warning" label="Warning progress" />
      <Progress variant="circular" value={60} tone="warning" label="60%" />
    </div>
  </div>

  <div>
    <h3>Danger</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      <Progress value={60} tone="danger" label="Danger progress" />
      <Progress variant="circular" value={60} tone="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

---
// GENERATED from packages/demos/progress/size.demo.tsx — edit the source, not this file.
import Progress from "@pindoba/astro-progress";
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
---
// GENERATED from packages/demos/progress/label.demo.tsx — edit the source, not this file.
import Progress from "@pindoba/astro-progress";
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: number, m: number) => `Step ${v} of ${m}`}
        label="Onboarding progress"
      />
      <Progress
        variant="circular"
        value={3}
        max={5}
        showLabel
        formatValue={(v: number, m: number) => `${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: number, m: number) => `${v} MB of ${m} MB`}
        tone="primary"
        label="Download progress"
      />
      <Progress
        variant="circular"
        value={42}
        max={100}
        showLabel
        formatValue={(v: number, _m: number) => `${v} MB`}
        tone="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.

---
// GENERATED from packages/demos/progress/custom.demo.tsx — edit the source, not this file.
import Progress from "@pindoba/astro-progress";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <div>
    <h3>Custom Max and Label</h3>
    <Progress
      value={3}
      max={10}
      showLabel
      formatValue={(v: number, m: number) => `${v} of ${m} steps`}
      label="Step progress"
      tone="primary"
    />
  </div>

  <div>
    <h3>Custom Track Styling</h3>
    <Progress
      value={70}
      tone="success"
      passThrough={{
        root: {
          style: css.raw({
            maxWidth: "400px",
          }),
        },
        track: {
          style: css.raw({
            borderRadius: "none",
          }),
        },
      }}
      label="Custom radius progress"
    />
  </div>

  <div>
    <h3>Accessible Label via Props</h3>
    <Progress
      variant="circular"
      value={42}
      tone="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>
  • Components
  • Blocks