component

Radio

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

Default

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

Appearance

All four appearance variants side by side.

radio (default)

button

tab-horizontal

tab-vertical

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

Size

Three sizes are available across all appearances.

radio appearance

button appearance

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

Button

appearance="button" composes with Panel — use emphasis (primary, secondary, ghost) and feedback (primary, success, danger, warning, neutral) to control the surface.

Primary (default)

Danger

Warning

Success

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

tab-horizontal and tab-vertical render a minimal accent-border indicator. Use feedback to change the accent color.

Tab Horizontal

Tab Vertical

Tab Horizontal — Danger feedback

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

With Badge

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

radio appearance

button appearance

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

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

radio appearance

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

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 root style via passThrough

Custom input style via passThrough

Custom props via passThrough

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

Props

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