A radio button component for single selection from a group. Supports four appearances: radio (default circular indicator), button (Panel-powered with full emphasis and feedback), tab-horizontal (bottom-border accent), and tab-vertical (left-border accent).
---
import Radio from "../Radio.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column" })}>
<Radio id="astro-def-1" name="astro-def" value="option1">Option 1</Radio>
<Radio id="astro-def-2" name="astro-def" value="option2" checked
>Option 2</Radio
>
<Radio id="astro-def-3" name="astro-def" value="option3" disabled
>Disabled</Radio
>
</div>All four appearance variants side by side.
---
import Radio from "../Radio.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>radio (default)</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Radio id="astro-app-r1" name="astro-app-radio" value="r1" checked
>Option 1</Radio
>
<Radio id="astro-app-r2" name="astro-app-radio" value="r2">Option 2</Radio
>
</div>
</div>
<div>
<h3>button</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-app-b1"
name="astro-app-button"
value="b1"
appearance="button"
checked
>
Option 1
</Radio>
<Radio
id="astro-app-b2"
name="astro-app-button"
value="b2"
appearance="button"
>
Option 2
</Radio>
<Radio
id="astro-app-b3"
name="astro-app-button"
value="b3"
appearance="button"
>
Option 3
</Radio>
</div>
</div>
<div>
<h3>tab-horizontal</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-app-th1"
name="astro-app-tabh"
value="th1"
appearance="tab-horizontal"
checked
>
Tab 1
</Radio>
<Radio
id="astro-app-th2"
name="astro-app-tabh"
value="th2"
appearance="tab-horizontal"
>
Tab 2
</Radio>
<Radio
id="astro-app-th3"
name="astro-app-tabh"
value="th3"
appearance="tab-horizontal"
>
Tab 3
</Radio>
</div>
</div>
<div>
<h3>tab-vertical</h3>
<div class={stack({ gap: "xs", direction: "column" })}>
<Radio
id="astro-app-tv1"
name="astro-app-tabv"
value="tv1"
appearance="tab-vertical"
checked
>
Dashboard
</Radio>
<Radio
id="astro-app-tv2"
name="astro-app-tabv"
value="tv2"
appearance="tab-vertical"
>
Settings
</Radio>
</div>
</div>
</div>Three sizes are available across all appearances.
---
import Radio from "../Radio.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>radio appearance</h3>
<div class={stack({ gap: "sm", direction: "row", align: "center" })}>
<Radio id="astro-size-sm" name="astro-size" value="sm" size="sm" checked
>Small</Radio
>
<Radio id="astro-size-md" name="astro-size" value="md" size="md"
>Medium</Radio
>
<Radio id="astro-size-lg" name="astro-size" value="lg" size="lg"
>Large</Radio
>
</div>
</div>
<div>
<h3>button appearance</h3>
<div class={stack({ gap: "sm", direction: "row", align: "center" })}>
<Radio
id="astro-sbtn-sm"
name="astro-sbtn"
value="sm"
appearance="button"
size="sm"
checked>Small</Radio
>
<Radio
id="astro-sbtn-md"
name="astro-sbtn"
value="md"
appearance="button"
size="md">Medium</Radio
>
<Radio
id="astro-sbtn-lg"
name="astro-sbtn"
value="lg"
appearance="button"
size="lg">Large</Radio
>
</div>
</div>
</div>appearance="button" composes with Panel — use emphasis (primary, secondary, ghost) and feedback (primary, success, danger, warning, neutral) to control the surface.
---
import Radio from "../Radio.astro";
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" })}>
<Radio
id="astro-btn-p1"
name="astro-btn-primary"
value="p1"
appearance="button"
checked
>
Option 1
</Radio>
<Radio
id="astro-btn-p2"
name="astro-btn-primary"
value="p2"
appearance="button"
>
Option 2
</Radio>
<Radio
id="astro-btn-p3"
name="astro-btn-primary"
value="p3"
appearance="button"
>
Option 3
</Radio>
</div>
</div>
<div>
<h3>Danger</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-btn-d1"
name="astro-btn-danger"
value="d1"
appearance="button"
feedback="danger"
checked
>
Option 1
</Radio>
<Radio
id="astro-btn-d2"
name="astro-btn-danger"
value="d2"
appearance="button"
feedback="danger"
>
Option 2
</Radio>
</div>
</div>
<div>
<h3>Warning</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-btn-w1"
name="astro-btn-warning"
value="w1"
appearance="button"
feedback="warning"
checked
>
Option 1
</Radio>
<Radio
id="astro-btn-w2"
name="astro-btn-warning"
value="w2"
appearance="button"
feedback="warning"
>
Option 2
</Radio>
</div>
</div>
<div>
<h3>Success</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-btn-s1"
name="astro-btn-success"
value="s1"
appearance="button"
feedback="success"
checked
>
Option 1
</Radio>
<Radio
id="astro-btn-s2"
name="astro-btn-success"
value="s2"
appearance="button"
feedback="success"
>
Option 2
</Radio>
</div>
</div>
</div>tab-horizontal and tab-vertical render a minimal accent-border indicator. Use feedback to change the accent color.
---
import Radio from "../Radio.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>Tab Horizontal</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-tab-h1"
name="astro-tab-horiz"
value="h1"
appearance="tab-horizontal"
checked
>
All
</Radio>
<Radio
id="astro-tab-h2"
name="astro-tab-horiz"
value="h2"
appearance="tab-horizontal"
>
Active
</Radio>
<Radio
id="astro-tab-h3"
name="astro-tab-horiz"
value="h3"
appearance="tab-horizontal"
>
Archived
</Radio>
</div>
</div>
<div>
<h3>Tab Vertical</h3>
<div class={stack({ gap: "xs", direction: "column" })}>
<Radio
id="astro-tab-v1"
name="astro-tab-vert"
value="v1"
appearance="tab-vertical"
checked
>
Dashboard
</Radio>
<Radio
id="astro-tab-v2"
name="astro-tab-vert"
value="v2"
appearance="tab-vertical"
>
Settings
</Radio>
<Radio
id="astro-tab-v3"
name="astro-tab-vert"
value="v3"
appearance="tab-vertical"
>
Profile
</Radio>
</div>
</div>
<div>
<h3>Tab Horizontal — Danger feedback</h3>
<div class={stack({ gap: "sm", direction: "row" })}>
<Radio
id="astro-tab-hd1"
name="astro-tab-danger"
value="hd1"
appearance="tab-horizontal"
feedback="danger"
checked
>
Errors
</Radio>
<Radio
id="astro-tab-hd2"
name="astro-tab-danger"
value="hd2"
appearance="tab-horizontal"
feedback="danger"
>
Warnings
</Radio>
</div>
</div>
</div>Badges inside radio components scale with the component’s size via fontSize: 1rem.
---
import Radio from "../Radio.astro";
import PindobaBadge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>radio appearance</h3>
<div class={stack({ gap: "sm", direction: "row", align: "center" })}>
<Radio
id="astro-badge-radio-sm"
name="astro-badge-radio"
value="sm"
size="sm"
checked
>
Small <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
<Radio
id="astro-badge-radio-md"
name="astro-badge-radio"
value="md"
size="md"
>
Medium <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
<Radio
id="astro-badge-radio-lg"
name="astro-badge-radio"
value="lg"
size="lg"
>
Large <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
</div>
</div>
<div>
<h3>button appearance</h3>
<div class={stack({ gap: "sm", direction: "row", align: "center" })}>
<Radio
id="astro-badge-btn-sm"
name="astro-badge-btn"
value="sm"
appearance="button"
size="sm"
checked
>
Small <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
<Radio
id="astro-badge-btn-md"
name="astro-badge-btn"
value="md"
appearance="button"
size="md"
>
Medium <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
<Radio
id="astro-badge-btn-lg"
name="astro-badge-btn"
value="lg"
appearance="button"
size="lg"
>
Large <PindobaBadge feedback="primary" size="sm">3</PindobaBadge>
</Radio>
</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.
---
import Radio from "../Radio.astro";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
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) => (
<Radio
id={`astro-lt-rbtn-${size}`}
appearance="button"
name="lt-demo-button"
value={size}
size={size}
fullWidth
checked={size === "md"}
>
<Stamp
slot="leading"
emphasis="adaptive"
size="xs"
shape="circle"
iconFill
>
<Icon name="lucide:mail" />
</Stamp>
Unread
<Badge slot="trailing" emphasis="adaptive" size="sm">
7
</Badge>
</Radio>
))
}
</div>
</div>
<div>
<h3>button appearance — leading only</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
{
sizes.map((size) => (
<Radio
id={`astro-lt-rbtn-leading-${size}`}
appearance="button"
name="lt-demo-button-leading"
value={size}
size={size}
fullWidth
checked={size === "md"}
>
<Stamp
slot="leading"
emphasis="adaptive"
size="xs"
shape="circle"
iconFill
>
<Icon name="lucide:mail" />
</Stamp>
Unread
</Radio>
))
}
</div>
</div>
<div>
<h3>button appearance — trailing only</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
{
sizes.map((size) => (
<Radio
id={`astro-lt-rbtn-trailing-${size}`}
appearance="button"
name="lt-demo-button-trailing"
value={size}
size={size}
fullWidth
checked={size === "md"}
>
Unread
<Badge slot="trailing" emphasis="adaptive" size="sm">
7
</Badge>
</Radio>
))
}
</div>
</div>
<div>
<h3>radio appearance</h3>
<div class={stack({ gap: "sm", direction: "column" })}>
{
sizes.map((size) => (
<Radio
id={`astro-lt-r-${size}`}
name="lt-demo-radio"
value={size}
size={size}
checked={size === "md"}
>
Pro plan
<Badge slot="trailing" emphasis="adaptive" size="sm">
$9/mo
</Badge>
</Radio>
))
}
</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.
---
import Radio from "../Radio.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>Custom root style via passThrough</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Radio
id="astro-cust-1"
name="astro-cust"
value="c1"
passThrough={{
root: {
style: { gap: "lg", padding: "sm" },
},
}}
>
Extra padding
</Radio>
</div>
</div>
<div>
<h3>Custom input style via passThrough</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Radio
id="astro-cust-2"
name="astro-cust"
value="c2"
passThrough={{
input: {
style: { width: "2em", height: "2em" },
},
}}
>
Larger indicator
</Radio>
</div>
</div>
<div>
<h3>Custom props via passThrough</h3>
<div class={stack({ gap: "md", direction: "column" })}>
<Radio
id="astro-cust-3"
name="astro-cust"
value="c3"
passThrough={{
root: {
props: { "data-testid": "my-radio" },
},
input: {
props: { "aria-label": "Custom radio button" },
},
}}
>
With data attributes
</Radio>
</div>
</div>
</div>| prop | type | default | req | description |
|---|---|---|---|---|
| appearance | "radio""button""tab-horizontal""tab-vertical" | "radio" | Visual style of the radio. radio: Default circular indicator. button: Styled as a button using Panel composition. tab-horizontal: Bottom-border accent tab. tab-vertical: Left-border accent tab. | |
| emphasis | "primary""secondary""ghost" | "primary" | Emphasis level for button appearance. primary: Solid accent background. secondary: Surface background with border. ghost: Transparent background. | |
| feedback | "primary""neutral""success""danger""warning" | "primary" | Color scheme applied via colorPalette. For radio and tab appearances this sets the indicator accent color. For button appearance this sets the Panel feedback color. | |
| size | "sm""md""lg" | "md" | Size of the component. | |
| fullWidth | boolean | false | Expand the radio to fill its container width. | |
| id | string | - | Unique identifier for the radio input, used for label association. | |
| passThrough | { root?: { style?: SystemStyleObject; props?: HTMLLabelAttributes }; input?: { style?: SystemStyleObject; props?: HTMLInputAttributes }; text?: { style?: SystemStyleObject; props?: HTMLAttributes } } | undefined | Per-slot style and attribute overrides. | |
| children | Snippet | undefined | Label content rendered next to the radio indicator. | |
| ...rest | Omit<HTMLInputAttributes, 'type' | 'size'> | - | Standard HTML input attributes (excluding type and size). |