component

Checkbox

A checkbox component for binary choices and multiple selections. Supports indeterminate state and provides proper accessibility with label association. Supports three appearances: checkbox (default square indicator), button (Panel-powered with full emphasis and tone), and switch (horizontal toggle with sliding knob).

Default

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

<div class={stack({ gap: "md", direction: "column" })}>
  <Checkbox id="astro-demo-checkbox-unchecked">Unchecked</Checkbox>
  <Checkbox id="astro-demo-checkbox-checked" checked>
    Checked
  </Checkbox>
  <Checkbox id="astro-demo-checkbox-indeterminate" indeterminate>
    Indeterminate
  </Checkbox>
  <Checkbox id="astro-demo-checkbox-disabled" disabled>
    Disabled
  </Checkbox>
</div>

Size

Three sizes are available across all appearances.

checkbox appearance

button appearance

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>checkbox appearance</h3>
    <div class={stack({ gap: "sm", direction: "row", align: "center" })}>
      <Checkbox id="astro-demo-csize-sm" size="sm">
        Small
      </Checkbox>
      <Checkbox id="astro-demo-csize-md" size="md" checked>
        Medium
      </Checkbox>
      <Checkbox id="astro-demo-csize-lg" size="lg">
        Large
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>button appearance</h3>
    <div class={stack({ gap: "sm", direction: "row", align: "center" })}>
      <Checkbox id="astro-demo-csbtn-sm" appearance="button" size="sm">
        Small
      </Checkbox>
      <Checkbox id="astro-demo-csbtn-md" appearance="button" size="md" checked>
        Medium
      </Checkbox>
      <Checkbox id="astro-demo-csbtn-lg" appearance="button" size="lg">
        Large
      </Checkbox>
    </div>
  </div>
</div>

Button

appearance="button" composes with Panel — use tone (primary, success, danger, warning, neutral) to control the surface.

Primary (default)

Danger

Warning

Success

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Primary (default)</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox id="astro-demo-cbtn-p1" appearance="button" checked>
        Option 1
      </Checkbox>
      <Checkbox id="astro-demo-cbtn-p2" appearance="button">
        Option 2
      </Checkbox>
      <Checkbox id="astro-demo-cbtn-p3" appearance="button">
        Option 3
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Danger</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-cbtn-d1"
        appearance="button"
        tone="danger"
        checked
      >
        Option 1
      </Checkbox>
      <Checkbox id="astro-demo-cbtn-d2" appearance="button" tone="danger">
        Option 2
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Warning</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-cbtn-w1"
        appearance="button"
        tone="warning"
        checked
      >
        Option 1
      </Checkbox>
      <Checkbox id="astro-demo-cbtn-w2" appearance="button" tone="warning">
        Option 2
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Success</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-cbtn-s1"
        appearance="button"
        tone="success"
        checked
      >
        Option 1
      </Checkbox>
      <Checkbox id="astro-demo-cbtn-s2" appearance="button" tone="success">
        Option 2
      </Checkbox>
    </div>
  </div>
</div>

Emphasis

For appearance="button", the emphasis prop controls how the unchecked and checked states relate to each other. primary (default) keeps today’s filled-accent look. secondary rides the colorPalette.surface.* ramp and swaps to the opposite ramp position when checked. tertiary stays on a neutral surface with accent-colored text and a muted border. The background prop still picks the starting position; hover and active step through the ramp from there.

The border prop owns the resting border. Secondary and tertiary default to "muted" and promote to "default" on the checked state; primary defaults to "default" everywhere (and the accent ring on checked is part of primary’s filled identity). Pass border="none" to remove the resting border entirely (great with tertiary for a borderless segmented look) or border="bold" | "accent" to intensify it.

Primary (default)

Secondary

Tertiary

Secondary + tone

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Primary (default)</h3>
    <div class={stack({ gap: "lg", direction: "row" })}>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox id="astro-demo-cbtn-e-p1" appearance="button" checked>
          Option 1
        </Checkbox>
        <Checkbox id="astro-demo-cbtn-e-p2" appearance="button">
          Option 2
        </Checkbox>
      </div>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox
          id="astro-demo-cbtn-e-pn1"
          appearance="button"
          tone="neutral"
          checked
        >
          Neutral 1
        </Checkbox>
        <Checkbox id="astro-demo-cbtn-e-pn2" appearance="button" tone="neutral">
          Neutral 2
        </Checkbox>
      </div>
    </div>
  </div>

  <div>
    <h3>Secondary</h3>
    <div class={stack({ gap: "lg", direction: "row" })}>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox
          id="astro-demo-cbtn-e-s1"
          appearance="button"
          emphasis="secondary"
          checked
        >
          Option 1
        </Checkbox>
        <Checkbox
          id="astro-demo-cbtn-e-s2"
          appearance="button"
          emphasis="secondary"
        >
          Option 2
        </Checkbox>
      </div>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox
          id="astro-demo-cbtn-e-sn1"
          appearance="button"
          emphasis="secondary"
          tone="neutral"
          checked
        >
          Neutral 1
        </Checkbox>
        <Checkbox
          id="astro-demo-cbtn-e-sn2"
          appearance="button"
          emphasis="secondary"
          tone="neutral"
        >
          Neutral 2
        </Checkbox>
      </div>
    </div>
  </div>

  <div>
    <h3>Tertiary</h3>
    <div class={stack({ gap: "lg", direction: "row" })}>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox
          id="astro-demo-cbtn-e-t1"
          appearance="button"
          emphasis="tertiary"
          checked
        >
          Option 1
        </Checkbox>
        <Checkbox
          id="astro-demo-cbtn-e-t2"
          appearance="button"
          emphasis="tertiary"
        >
          Option 2
        </Checkbox>
      </div>
      <div class={stack({ gap: "sm", direction: "row" })}>
        <Checkbox
          id="astro-demo-cbtn-e-tn1"
          appearance="button"
          emphasis="tertiary"
          tone="neutral"
          checked
        >
          Neutral 1
        </Checkbox>
        <Checkbox
          id="astro-demo-cbtn-e-tn2"
          appearance="button"
          emphasis="tertiary"
          tone="neutral"
        >
          Neutral 2
        </Checkbox>
      </div>
    </div>
  </div>

  <div>
    <h3>Secondary + tone</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-cbtn-e-fbn"
        appearance="button"
        emphasis="secondary"
        tone="neutral"
        checked
      >
        Neutral
      </Checkbox>
      <Checkbox
        id="astro-demo-cbtn-e-fbd"
        appearance="button"
        emphasis="secondary"
        tone="danger"
        checked
      >
        Danger
      </Checkbox>
      <Checkbox
        id="astro-demo-cbtn-e-fbw"
        appearance="button"
        emphasis="secondary"
        tone="warning"
        checked
      >
        Warning
      </Checkbox>
      <Checkbox
        id="astro-demo-cbtn-e-fbs"
        appearance="button"
        emphasis="secondary"
        tone="success"
        checked
      >
        Success
      </Checkbox>
    </div>
  </div>
</div>

Switch

appearance="switch" renders a horizontal toggle with a sliding knob — ideal for instant on/off settings. It still uses <input type="checkbox"> under the hood, so standard form semantics and checked/disabled behavior are unchanged.

Primary (default)

Success

Warning

Danger

Sizes

Disabled

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Primary (default)</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox id="astro-demo-csw-p1" appearance="switch" checked>
        Wi-Fi
      </Checkbox>
      <Checkbox id="astro-demo-csw-p2" appearance="switch">
        Bluetooth
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Success</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-csw-s1"
        appearance="switch"
        tone="success"
        checked
      >
        Notifications
      </Checkbox>
      <Checkbox id="astro-demo-csw-s2" appearance="switch" tone="success">
        Sync
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Warning</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-csw-w1"
        appearance="switch"
        tone="warning"
        checked
      >
        Beta features
      </Checkbox>
      <Checkbox id="astro-demo-csw-w2" appearance="switch" tone="warning">
        Experimental
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Danger</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox
        id="astro-demo-csw-d1"
        appearance="switch"
        tone="danger"
        checked
      >
        Delete on save
      </Checkbox>
      <Checkbox id="astro-demo-csw-d2" appearance="switch" tone="danger">
        Purge cache
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Sizes</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox id="astro-demo-csw-sm" appearance="switch" size="sm" checked>
        Small
      </Checkbox>
      <Checkbox id="astro-demo-csw-md" appearance="switch" size="md" checked>
        Medium
      </Checkbox>
      <Checkbox id="astro-demo-csw-lg" appearance="switch" size="lg" checked>
        Large
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Disabled</h3>
    <div class={stack({ gap: "sm", direction: "row" })}>
      <Checkbox id="astro-demo-csw-dis1" appearance="switch" disabled>
        Unchecked
      </Checkbox>
      <Checkbox id="astro-demo-csw-dis2" appearance="switch" disabled checked>
        Checked
      </Checkbox>
    </div>
  </div>
</div>

With Badge

Badges inside checkbox components scale with the component’s size via fontSize: 1rem.

checkbox appearance

button appearance

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>checkbox appearance</h3>
    <div class={stack({ gap: "sm", direction: "row", align: "center" })}>
      <Checkbox id="astro-demo-badge-cb-sm" size="sm" checked>
        Small{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
      <Checkbox id="astro-demo-badge-cb-md" size="md" checked>
        Medium{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
      <Checkbox id="astro-demo-badge-cb-lg" size="lg" checked>
        Large{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>button appearance</h3>
    <div class={stack({ gap: "sm", direction: "row", align: "center" })}>
      <Checkbox
        id="astro-demo-badge-cbtn-sm"
        appearance="button"
        size="sm"
        checked
      >
        Small{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
      <Checkbox
        id="astro-demo-badge-cbtn-md"
        appearance="button"
        size="md"
        checked
      >
        Medium{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
      <Checkbox
        id="astro-demo-badge-cbtn-lg"
        appearance="button"
        size="lg"
        checked
      >
        Large{" "}
        <Badge tone="primary" size="sm">
          3
        </Badge>
      </Checkbox>
    </div>
  </div>
</div>

Leading & Trailing slots

Use the leading and trailing slots to position icons, stamps, or badges at the start and end of the label. With emphasis="adaptive", badges and stamps follow the parent’s color in both unchecked and checked states. The trailing slot pins to the end of the row, which pairs well with fullWidth.

button appearance — both slots

button appearance — leading only

button appearance — trailing only

checkbox appearance

---
// GENERATED from packages/demos/checkbox/leading-trailing.demo.tsx — edit the source, not this file.
import Checkbox from "@pindoba/astro-checkbox";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Bell } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";

const sizes = ["sm", "md", "lg"] as const;
---

<div class={stack({ gap: "lg", direction: "column" })}>
  <div>
    <h3>button appearance — both slots</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      {sizes.map((size) => (
        <Checkbox
          id={`astro-demo-lt-cbtn-${size}`}
          appearance="button"
          size={size}
          fullWidth
          checked={size === "md"}
        >
          <Stamp slot="leading" emphasis="adaptive" size="xs" shape="circle">
            <Bell />
          </Stamp>
          <Badge slot="trailing" emphasis="adaptive" size="sm">
            12
          </Badge>
          Notifications
        </Checkbox>
      ))}
    </div>
  </div>

  <div>
    <h3>button appearance — leading only</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      {sizes.map((size) => (
        <Checkbox
          id={`astro-demo-lt-cbtn-leading-${size}`}
          appearance="button"
          size={size}
          fullWidth
          checked={size === "md"}
        >
          <Stamp slot="leading" emphasis="adaptive" size="xs" shape="circle">
            <Bell />
          </Stamp>
          Notifications
        </Checkbox>
      ))}
    </div>
  </div>

  <div>
    <h3>button appearance — trailing only</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      {sizes.map((size) => (
        <Checkbox
          id={`astro-demo-lt-cbtn-trailing-${size}`}
          appearance="button"
          size={size}
          fullWidth
          checked={size === "md"}
        >
          <Badge slot="trailing" emphasis="adaptive" size="sm">
            12
          </Badge>
          Notifications
        </Checkbox>
      ))}
    </div>
  </div>

  <div>
    <h3>checkbox appearance</h3>
    <div class={stack({ gap: "sm", direction: "column" })}>
      {sizes.map((size) => (
        <Checkbox
          id={`astro-demo-lt-cb-${size}`}
          size={size}
          checked={size === "md"}
        >
          <Badge slot="trailing" emphasis="adaptive" size="sm">
            5
          </Badge>
          Tasks
        </Checkbox>
      ))}
    </div>
  </div>
</div>

Custom Styling

The passThrough prop provides per-slot escape hatches: style accepts a Panda CSS SystemStyleObject merged with the base styles, and props forwards arbitrary HTML attributes onto the element.

Custom input style via passThrough

Custom props via passThrough

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

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Custom input style via passThrough</h3>
    <div class={stack({ gap: "md", direction: "column" })}>
      <Checkbox
        id="astro-demo-checkbox-custom-style"
        passThrough={{
          input: {
            style: { borderRadius: "full" },
          },
        }}
      >
        Rounded checkbox
      </Checkbox>
    </div>
  </div>

  <div>
    <h3>Custom props via passThrough</h3>
    <div class={stack({ gap: "md", direction: "column" })}>
      <Checkbox
        id="astro-demo-checkbox-custom-props"
        passThrough={{
          root: {
            props: { "data-testid": "my-checkbox" },
          },
          input: {
            props: { "aria-label": "Accept terms and conditions" },
          },
        }}
      >
        Accept terms
      </Checkbox>
    </div>
  </div>
</div>

Setting indeterminate from a script (Astro)

indeterminate is a DOM property, so assigning input.indeterminate = true after load emits no event and the checkbox never gets aria-checked="mixed". In Astro, or any vanilla script, call setCheckboxIndeterminate(input, true) from @pindoba/astro-checkbox/runtime (also exported by @pindoba/core-checkbox) instead: it sets the property and syncs aria-checked (mixed while indeterminate, removed otherwise), and a later click still releases the mixed state.

  • Components
  • Blocks