forms
Radio
Native radio option with inline default layout or selectable card panels, label, description, and attribute forwarding.
Copied!
Examples
Inline group
Default variant as a vertical radio list.
Copied!
<fieldset class="max-w-sm space-y-3">
<legend class="text-sm font-medium text-fg">Billing cycle</legend>
<x-ui.radio name="cycle" value="monthly" label="Monthly" checked />
<x-ui.radio name="cycle" value="yearly" label="Yearly" />
</fieldset>
<fieldset class="max-w-sm space-y-3">
<legend class="text-sm font-medium text-fg">Billing cycle</legend>
<div class="inline-flex items-start gap-3">
<div class="relative mt-0.5 inline-flex size-4 shrink-0">
<input id="cycle-monthly" type="radio" name="cycle" value="monthly" checked class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
<span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
<span class="dot size-2 rounded-full bg-brand opacity-0"></span>
</span>
</div>
<label for="cycle-monthly" class="text-sm font-medium text-fg">Monthly</label>
</div>
<div class="inline-flex items-start gap-3">
<div class="relative mt-0.5 inline-flex size-4 shrink-0">
<input id="cycle-yearly" type="radio" name="cycle" value="yearly" class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
<span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
<span class="dot size-2 rounded-full bg-brand opacity-0"></span>
</span>
</div>
<label for="cycle-yearly" class="text-sm font-medium text-fg">Yearly</label>
</div>
</fieldset>
Card variant
Selectable panels highlighted with :has(input:checked).
Copied!
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
<legend class="mb-1 text-sm font-medium text-fg">Deployment region</legend>
<x-ui.radio
variant="card"
name="region"
value="sg"
label="Singapore"
description="Lowest latency for SEA users."
checked
/>
<x-ui.radio
variant="card"
name="region"
value="eu"
label="Frankfurt"
description="EU data residency friendly."
/>
</fieldset>
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
<legend class="mb-1 text-sm font-medium text-fg">Deployment region</legend>
<label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-sm has-[:checked]:border-brand has-[:checked]:bg-brand/5">
<span class="relative mt-0.5 inline-flex size-4 shrink-0">
<input id="region-sg" type="radio" name="region" value="sg" checked class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
<span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
<span class="dot size-2 rounded-full bg-brand opacity-0"></span>
</span>
</span>
<span>
<span class="block text-sm font-medium text-fg">Singapore</span>
<span class="mt-0.5 block text-sm text-fg-muted">Lowest latency for SEA users.</span>
</span>
</label>
<label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-sm has-[:checked]:border-brand has-[:checked]:bg-brand/5">
<span class="relative mt-0.5 inline-flex size-4 shrink-0">
<input id="region-eu" type="radio" name="region" value="eu" class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
<span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
<span class="dot size-2 rounded-full bg-brand opacity-0"></span>
</span>
</span>
<span>
<span class="block text-sm font-medium text-fg">Frankfurt</span>
<span class="mt-0.5 block text-sm text-fg-muted">EU data residency friendly.</span>
</span>
</label>
</fieldset>
Disabled option
Disabled state on a single option.
Copied!
Contact sales to unlock this plan.
<x-ui.radio
name="plan_demo"
value="enterprise"
label="Enterprise"
description="Contact sales to unlock this plan."
disabled
/>
<div class="inline-flex items-start gap-3 opacity-50">
<div class="relative mt-0.5 inline-flex size-4 shrink-0">
<input id="plan-enterprise" type="radio" name="plan_demo" value="enterprise" disabled class="peer absolute inset-0 z-10 size-4 cursor-not-allowed opacity-0">
<span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm" aria-hidden="true">
<span class="size-2 rounded-full bg-brand opacity-0"></span>
</span>
</div>
<div>
<label for="plan-enterprise" class="block text-sm font-medium text-fg">Enterprise</label>
<p class="mt-0.5 text-sm text-fg-muted">Contact sales to unlock this plan.</p>
</div>
</div>
Props API
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | default|card | default | Inline list item or bordered selectable card. Cards highlight with :has(input:checked). |
| label | string|null | null | Visible label linked via for/id (or wrapping label for card). |
| description | string|null | null | Supporting text referenced by aria-describedby. |
Accessibility notes
Architected with WCAG 2.2 AA patterns in mind. These notes document the exact keyboard and ARIA behavior for this component.
- Uses a native radio input so arrow keys move within a name group and Space selects the focused option.
- Default variant links label for to input id. Card variant wraps the control in a label for a larger hit target.
- Description is linked with aria-describedby when provided.
- Pass-through attributes (name, value, checked, disabled, required, x-model, wire:model) land on the input.
- Compose a group by rendering multiple radios that share the same name.
- Keyboard: Tab enters the group. Arrow keys change the selected option within the group.
Built by Praesepe
Architecture & custom software partner
Praesepe uses these exact UI primitives to ship SME dashboards, inventory systems, invoicing desks, and internal tools faster, without rebuilding a design system for every client.
- Same Blade + Tailwind 4 + Alpine 3 stack you see in this docs site
- Opinionated for Indonesian SME ops: invoices, stock, roles, approvals
- Architected with WCAG 2.2 AA patterns in mind for keyboard and ARIA