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).
---
// 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>Three sizes are available across all appearances.
---
// 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>appearance="button" composes with Panel — use tone (primary, success, danger, warning, neutral) to control the surface.
---
// 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>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.
---
// 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>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.
---
// 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>Badges inside checkbox components scale with the component’s size via fontSize: 1rem.
---
// 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>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.
---
// 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>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.
---
// 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>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.