Radio

Native <input type="radio"> repainted with appearance: none. The inner dot is drawn with a radial-gradient background so the control needs no extra pseudo-element and the dot colour tracks the primary combination across every theme.

Live

Radio group with disabled option

<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="radio" name="plan" checked /> Free
</label>
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="radio" name="plan" /> Pro
</label>
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="radio" name="plan" disabled /> Enterprise (disabled)
</label>
</div>
@use '@adnap/krysalicss' with (
  $feature-list: ('base-reset', 'base-global', 'element-radio')
);
Playground

Markup

markup
<label>
  <input type="radio" name="plan" checked> Free
</label>
<label>
  <input type="radio" name="plan"> Pro
</label>
<label>
  <input type="radio" name="plan" disabled> Enterprise (disabled)
</label>

When to use

  • Mutually exclusive choices within a group (exactly one selected at a time). Group via the shared name attribute.
  • For independent on/off choices, use Checkbox.
  • For four or more options, consider Select instead — it scales better at narrow widths.

Variables

VariableDefaultNotes
$selector'input[type="radio"]'Attribute selector; class-based override possible.
$size1remCircle diameter. Width and height match.
$border-width$global-border-width (1px)Outer ring stroke.
$dot-size50%Inner dot diameter as a percentage of the control. Drawn via radial-gradient.
$focus-outline-width$global-focus-outline-width (2px)Focus-visible ring width.
$focus-outline-style$global-focus-outline-style (solid)Focus ring style.
$focus-outline-offset$global-focus-outline-offset (2px)Gap between border and ring.
$disabled-opacity$global-disabled-opacity (0.6)Routed through controls.disabledControl().
$border-mix$global-border-mix (50%)Border tint vs. currentColor. Clears WCAG 1.4.11 (3:1) on shipped themes.

Override example

app.scss
@use '@adnap/krysalicss/element/radio' with (
  $size: 1.125rem,
  $dot-size: 45%,
  $border-mix: 60%,
);

Tokens consumed

TokenUsed for
--kc-primary-bgBorder colour and inner-dot fill when :checked.
--kc-focus-ringPainted on :focus-visible. Falls back to --kc-fg then currentColor.
--kc-danger-bgBorder tint when the control matches :user-invalid or [aria-invalid="true"].

Accessibility

  • Anchored on native <input type="radio">: arrow-key navigation between radios of the same name, label-click toggling, and group semantics ship for free.
  • Selected state is conveyed by both border colour and the inner dot, so the cue isn't colour-only (WCAG 1.4.1).
  • Always wrap a related set in <fieldset> with <legend> for screen-reader grouping; the framework styles the inputs but does not impose the fieldset.
  • Focus ring uses --kc-focus-ring with a fallback chain into --kc-fg / currentColor, staying visible across themes (WCAG 2.4.11).
  • The 1rem hit box is below the WCAG 2.5.5 (44 × 44 px) touch-target floor. Wrap each input in a <label> with a comfortable hit-area.
  • Forced-colors mode inherits the browser default for native radios; the framework does not repaint inside (forced-colors: active).

See also

  • Checkbox — independent on/off choice (multi-select).
  • Select — dropdown one-of-many; reach for it when the group has more than five entries.
  • Forms overview — full form-control roster.
  • Field wrapper — pairs every form control with label + help text.