Headless radio
Headless radio keeps the native <input type="radio"> element in place and adds a stable contract for checked, readonly, invalid, and focus-visible styling.
- Native single-choice behavior through shared
namevalues. - Readonly support that stays focusable while reverting user changes.
- Consistent
data-*hooks for custom wrappers and visual systems.
Installation
Import only the radio primitive when the surrounding layout and labels belong to your app.
Primitive import
ts
import { TngRadio } from '@tailng-ui/primitives';
Basic usage
The directive attaches directly to a native radio input, but you almost always want a clickable label or fieldset around it so the group stays readable and easy to target.
Minimal attachment
html
<input tngRadio name="plan" value="starter" />
Recommended grouped pattern
html
<fieldset class="radio-group">
<legend>Billing plan</legend>
<label class="radio-row">
<input tngRadio name="billing-plan" value="starter" />
<span>Starter</span>
</label>
<label class="radio-row">
<input tngRadio name="billing-plan" value="pro" [checked]="true" />
<span>Pro</span>
</label>
</fieldset>
Style variants
The same headless radio behavior rendered inside a plain CSS shell and a Tailwind utility shell.
Billing plan shell (Plain-CSS)
Billing plan shell (Tailwind CSS)
Accessibility baseline
- Use a shared
nameso browsers enforce single-selection semantics. - Wrap related radios in a
<fieldset>and<legend>when the label belongs to the group. data-focus-visiblekeeps keyboard-only focus styling straightforward.