Skip to content

Radio

A native <input type="radio"> styled via class + data attributes from the optional styles layer. Two variants, three sizes, color theming, high-contrast and disabled states. Indicator is a currentColor dot drawn via ::after at scale(0.4) — no glyph, no SVG, no JS.

Install

import "elements-kit/ui/styles/theme.css";
import "elements-kit/ui/styles/scaling.css";
import "elements-kit/ui/styles/radius.css";
import "elements-kit/ui/styles/space.css";
import "elements-kit/ui/styles/typography.css";
import "elements-kit/ui/styles/cursor.css";
import "elements-kit/ui/styles/unset.css";
// pick a neutral palette for --neutral-* (must match data-neutral on root):
import "elements-kit/ui/styles/palette/gray.css";
import "elements-kit/ui/styles/neutral/gray.css";
// import any color scales you want to use for accent theming:
import "elements-kit/ui/styles/palette/mint.css";
import "elements-kit/ui/styles/accent/mint.css";
// and the radio itself:
import "elements-kit/ui/radio/radio.css";

See Styles for the full token system, accent/gray scale lists, and theming knobs.

API

<input
type="radio"
class="unset x-radio"
name="plan"
value="hobby"
data-variant="surface"
data-size="2"
/>

The unset class (Styles → Unset native styles) clears the browser’s default <input> rendering so .x-radio styles render predictably. Skip it and you’ll see the OS-native radio leak through.

Radios in the same group share a name= attribute. The browser handles single-selection within the group natively — no JS, no .x-radio-group class needed.

AttributeValues
data-variantsurface (default), soft
data-size1, 2 (default), 3
data-accent (on a parent or the input)any imported color scale (mint, blue, iris, …)
data-high-contrastmodifier — boosts contrast against the page background
disabled / data-disablednative :disabled state, or data-disabled on non-input elements

Sizing

data-sizebox
1--space-4 × 0.875 (~14px)
2 (default)--space-4 (~16px)
3--space-4 × 1.25 (~20px)

The dot is always 40% of the box (scale(0.4) transform), so it scales with the box without per-size tuning.

Variants

  • surface (default) — 1px hairline border using --neutral-a7. Background fills with --accent-9 and contrast dot on :checked.
  • soft — tinted background using --accent-a4 at every state. Dot uses --accent-a11 so it reads at low emphasis.

States

Radios don’t have an :indeterminate state — that’s checkbox-only. Otherwise the state set is the same as the Checkbox: :checked, :disabled / [data-disabled], :focus-visible (2px --focus-8 outline at +2px offset).

With a label

Wrap each input in a <label> (or pair via for=) so text and box are both clickable. Group radios by giving them the same name=:

<label style="display: inline-flex; align-items: center; gap: 0.5em;">
<input type="radio" class="unset x-radio" name="plan" value="hobby" />
<span>Hobby</span>
</label>
<label style="display: inline-flex; align-items: center; gap: 0.5em;">
<input type="radio" class="unset x-radio" name="plan" value="pro" />
<span>Pro</span>
</label>

For vertical stacks, wrap the labels in a flex column with gap: var(--space-1). The kit deliberately doesn’t ship a .x-radio-group class — flex + gap is one line.

Theming

Set data-accent="<color>" on the input or any ancestor (e.g. <body>) to theme the accent fill. Light/dark flips automatically via the .dark class on a parent — see Light & dark.

<body data-accent="iris">
<input type="radio" class="unset x-radio" name="x" checked />
</body>

Accessibility

  • Every radio needs a <label> — either wrapping the input or paired via for=.
  • Radios in a logical group must share a name= attribute. Without it the browser treats each as a standalone toggle.
  • For a multi-radio group, preselect one option by default. Screen readers announce “0 of N selected” otherwise, which reads as broken to most users.