Radio
A single-choice input: a styled native radio plus a group that manages roving focus and arrow-key navigation.
Live demo
Radio is a styled native <input type="radio">: the real input drives state and keyboard semantics while a custom indicator paints the selected dot in any of the six semantic tones. RadioGroup wraps a set of radios in a role="radiogroup", lays them out vertically or horizontally, and owns the WAI-ARIA roving-tabindex pattern: the group is a single Tab stop and arrow keys move selection between options, wrapping at the ends.
Each radio carries its own label (via the label attribute or default-slot text), an invalid state, and a disabled state; the group can disable as a whole. State lives on the native input, so form participation and submission come for free.
When to use
How this component composes with the rest of the set.
Props
10 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Variants
accent
The checked indicator fills with the accent tone (the default).
neutral
Neutral-toned checked indicator.
danger
Danger-toned checked indicator.
success
Success-toned checked indicator.
warn
Warn-toned checked indicator.
info
Info-toned checked indicator.
horizontal
Lays the group's options in a wrapping row instead of a column.
Sizes
sm
Compact.
md
Default.
lg
Large.
States
hover
Pointer over the radio: the indicator border and fill shift toward the hover tint.
checked
Selected: the indicator fills with the tone color and the dot scales in.
focus-visible
Keyboard focus: a token-colored ring on the indicator plus a transparent outline promoted in forced-colors mode.
invalid
Marked invalid: the indicator border turns danger-colored.
disabled
Non-interactive: muted fill and ink, no pointer.
Anatomy
The named parts that make up the component, with their selectors.
radio
The <label> wrapper for a single option, carrying the tone, size, and invalid classes.
control
The native <input type="radio">, visually hidden but present for state, focus, and form submission.
indicator
The painted circle that fills with the tone color and shows the dot when checked.
label
The text content for the option, taken from the label attribute or the default slot.
group
The role="radiogroup" container plus its optional label, laid out by orientation.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xoji-default register
(derive(xojiDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--accent-2
--accent-2-fg
--accent-3
--accent-3-fg
--accent-4
--accent-4-fg
--accent-fg
--black
--black-fg
--blue
--blue-fg
--border-normal
--border-thick
--brown
--brown-fg
--cyan
--cyan-fg
--danger
--danger-fg
--duration-fast
--ease-emphasized
--ease-standard
--fg-0
--fg-1
--fg-disabled
--field-bg
--field-border
--font-sans
--gray
--gray-fg
--green
--green-fg
--info
--info-fg
--leading-normal
--line
--line-2
--neutral
--neutral-fg
--orange
--orange-fg
--pink
--pink-fg
--purple
--purple-fg
--radius-full
--red
--red-fg
--ring
--space-1
--space-2
--space-3
--space-4
--state-disabled
--state-hover
--success
--success-fg
--text-body
--text-lg
--text-sm
--warn
--warn-fg
--weight-medium
--white
--white-fg
--yellow
--yellow-fg
Slots
On a Radio, the option's label text. On a RadioGroup, the radio options.
Accessibility
Code
Grouped choice and tones
A labelled vertical group with roving focus, plus standalone toned radios.
<xoji-radio-group label="Plan" orientation="vertical">
<xoji-radio name="plan" value="free" label="Free" checked></xoji-radio>
<xoji-radio name="plan" value="pro" label="Pro" tone="accent"></xoji-radio>
<xoji-radio name="plan" value="team" label="Team"></xoji-radio>
</xoji-radio-group>
<xoji-radio name="confirm" value="yes" tone="success">I agree to the terms</xoji-radio>
<xoji-radio name="confirm" value="no" tone="danger" invalid label="Decline"></xoji-radio>
<script lang="ts">
import { Radio, RadioGroup } from "@xoji/svelte";
let plan = $state("free");
</script>
<RadioGroup label="Plan" orientation="vertical" onchange={(e) => (plan = (e.target as HTMLInputElement).value)}>
<Radio name="plan" value="free" label="Free" checked={plan === "free"} />
<Radio name="plan" value="pro" label="Pro" tone="accent" checked={plan === "pro"} />
<Radio name="plan" value="team" label="Team" checked={plan === "team"} />
</RadioGroup>
<Radio name="confirm" value="yes" tone="success">I agree to the terms</Radio>
---
import { Radio, RadioGroup } from "@xoji/astro";
---
<RadioGroup label="Plan" orientation="vertical">
<Radio name="plan" value="free" label="Free" checked />
<Radio name="plan" value="pro" label="Pro" tone="accent" />
<Radio name="plan" value="team" label="Team" />
</RadioGroup>
<Radio name="confirm" value="yes" tone="success">I agree to the terms</Radio>