Skip to main content

Radio

Info:Controls
73

A single-choice input: a styled native radio plus a group that manages roving focus and arrow-key navigation.

radio group single choice option choice

Live demo

live · @xtyle/astro

Radio

A single-choice group

Option cards

card + description renders each choice as a full-width card with a title and a line of explanation, taking an accent ring when selected. The group still owns selection, roving focus, and the a11y, so a title-plus-explanation chooser doesn't have to be hand-rolled.

Horizontal

Every tone

Accents

Statuses

Named hues

Sizes & states

sm

md

lg

invalid

disabled

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 optional description (a secondary explanation line wired to aria-describedby), an invalid state, and a disabled state; the group can disable as a whole. Add card to render a radio as a full-width option card that takes an accent ring and tint when selected: card plus description is the title-and-explanation option-card pattern, selection and a11y owned by the component instead of hand-rolled. 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.

Wrap radios in a RadioGroup so arrow keys move selection and the set is a single Tab stop.
Pair with Field to attach a shared label, help text, and error to the whole group.
Give every radio in a set the same name so native form submission yields one value.

Props

12 props, straight from the manifest.

PropTypeDefaultBindingsDescription
tone FullTone
accent neutral danger success warn info accent-2 accent-3 accent-4 red orange yellow green blue purple brown pink cyan gray white black
accent
html svelte astro
Semantic color of the checked indicator. Radio only.
size Size
sm md lg
md
html svelte astro
Control size. Radio only.
name string
html svelte astro
The shared form name that groups native radios into one choice. Radio only.
value string on
html svelte astro
The submitted value when this radio is checked. Radio only.
checked boolean false
html svelte astro
Whether this radio is selected. Two-way bindable in Svelte. Radio only.
invalid boolean false
html svelte astro
Marks the radio invalid, danger-colored ring plus `aria-invalid`. Radio only.
label string
html svelte astro
Accessible label text. On a Radio it is the option label; on a RadioGroup it names the whole group.
labelledby string
html svelte astro
ID of an external element labelling the radio or group, when `label` is not used.
description string
html svelte astro
A secondary line of explanation under the label, wired to the input's `aria-describedby`. Radio only.
card boolean false
html svelte astro
Renders the radio as a full-width option card: a bordered box that takes an accent ring and tint when selected. Pairs with `description` for the title-plus-explanation option-card pattern. Radio only.
orientation "vertical" | "horizontal"
vertical horizontal
vertical
html svelte astro
Layout direction and `aria-orientation` of the group. RadioGroup only.
disabled boolean false
html svelte astro
Disables interaction. On a Radio it disables that option; on a RadioGroup it marks the whole group `aria-disabled`.

Appearance

Variants

card

.xtyle-radio--card

A full-width option card: a bordered box that takes an accent ring and tint when selected. Pairs with description for the option-card pattern.

accent

.xtyle-radio--accent

The checked indicator fills with the accent tone (the default).

neutral

.xtyle-radio--neutral

Neutral-toned checked indicator.

danger

.xtyle-radio--danger

Danger-toned checked indicator.

success

.xtyle-radio--success

Success-toned checked indicator.

warn

.xtyle-radio--warn

Warn-toned checked indicator.

info

.xtyle-radio--info

Info-toned checked indicator.

horizontal

.xtyle-radio-group--horizontal

Lays the group's options in a wrapping row instead of a column.

Sizes

sm

.xtyle-radio--sm

Compact.

md

default
.xtyle-radio

Default.

lg

.xtyle-radio--lg

Large.

States

hover

.xtyle-radio:hover .xtyle-radio__indicator

Pointer over the radio: the indicator border and fill shift toward the hover tint.

checked

.xtyle-radio__control:checked ~ .xtyle-radio__indicator

Selected: the indicator fills with the tone color and the dot scales in.

focus-visible

.xtyle-radio__control:focus-visible ~ .xtyle-radio__indicator

Keyboard focus: a token-colored ring on the indicator plus a transparent outline promoted in forced-colors mode.

invalid

.xtyle-radio--invalid .xtyle-radio__indicator

Marked invalid: the indicator border turns danger-colored.

disabled

.xtyle-radio__control:disabled ~ .xtyle-radio__indicator, .xtyle-radio[aria-disabled="true"]

Non-interactive: muted fill and ink, no pointer.

Anatomy

The named parts that make up the component, with their selectors.

radio

.xtyle-radio

The <label> wrapper for a single option, carrying the tone, size, and invalid classes.

--font-sans --text-body --leading-normal --fg-0 --space-2

control

.xtyle-radio__control

The native <input type="radio">, visually hidden but present for state, focus, and form submission.

indicator

.xtyle-radio__indicator

The painted circle that fills with the tone color and shows the dot when checked.

--border-normal --field-border --field-bg --radius-full --duration-fast --ease-standard --ease-emphasized

label

.xtyle-radio__label

The text content for the option, taken from the label attribute or the default slot.

description

.xtyle-radio__description

The optional secondary explanation line under the label; hidden when empty.

--text-sm --fg-2 --leading-normal

group

.xtyle-radio-group

The role="radiogroup" container plus its optional label, laid out by orientation.

--font-sans --space-2 --space-4 --text-sm --weight-medium --fg-1 --leading-normal

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 73/73 consumed tokens produced default register: 299 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { 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-bg --accent-fg --bg-1 --black --black-fg --blue --blue-fg --border-normal --border-thick --border-thin --brown --brown-fg --cyan --cyan-fg --danger --danger-fg --duration-fast --ease-emphasized --ease-standard --fg-0 --fg-1 --fg-2 --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 --radius-md --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

default
html svelte astro

On a Radio, the option's label text. On a RadioGroup, the radio options.

Accessibility

Each Radio is a native <input type="radio">, so selection, keyboard, and form semantics come for free.
RadioGroup is a role="radiogroup" with aria-orientation; it implements the roving-tabindex pattern: one Tab stop, arrows move selection and wrap.
A radio needs an accessible name from label, default-slot text, labelledby, or aria-label; the binding warns at runtime when none is present.
The group needs a name from label, labelledby, or aria-label; the binding warns when it is missing.
invalid sets aria-invalid on the input alongside the danger-colored ring.
The indicator is decorative (aria-hidden); state is conveyed by the native input's checked state.
Focus shows a token ring on the indicator plus a transparent outline the forced-colors base rule promotes to a real system outline.

Code

Grouped choice and tones

A labelled vertical group with roving focus, plus standalone toned radios.

<xtyle-radio-group label="Plan" orientation="vertical">
	<xtyle-radio name="plan" value="free" label="Free" checked></xtyle-radio>
	<xtyle-radio name="plan" value="pro" label="Pro" tone="accent"></xtyle-radio>
	<xtyle-radio name="plan" value="team" label="Team"></xtyle-radio>
</xtyle-radio-group>

<xtyle-radio name="confirm" value="yes" tone="success">I agree to the terms</xtyle-radio>

<xtyle-radio name="confirm" value="no" tone="danger" invalid label="Decline"></xtyle-radio>
<script lang="ts">
	import { Radio, RadioGroup } from "@xtyle/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 "@xtyle/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>

Option cards

card + description renders each choice as a full-width card with a title and a line of explanation, taking an accent ring when selected — the option-card pattern with selection and a11y owned by the component.

<xtyle-radio-group label="Sync mode">
	<xtyle-radio card name="sync" value="linked" label="Linked" checked
		description="Changes sync both ways automatically, the moment either side updates."></xtyle-radio>
	<xtyle-radio card name="sync" value="confirm" label="Confirm each"
		description="Review and approve every change before it is applied to the other side."></xtyle-radio>
</xtyle-radio-group>
<script lang="ts">
	import { Radio, RadioGroup } from "@xtyle/svelte";
	let mode = $state("linked");
</script>

<RadioGroup label="Sync mode" onchange={(e) => (mode = (e.target as HTMLInputElement).value)}>
	<Radio card name="sync" value="linked" label="Linked" checked={mode === "linked"}
		description="Changes sync both ways automatically, the moment either side updates." />
	<Radio card name="sync" value="confirm" label="Confirm each" checked={mode === "confirm"}
		description="Review and approve every change before it is applied to the other side." />
</RadioGroup>