Skip to content

HyRadio ​

Usage examples on this page are written for Lit / plain HTML (<hy-radio>). The same component ships as HyRadio in @whitespaceux/harmony-react (native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.

A form-associated radio button component that provides single-choice selection within a group. Supports comprehensive form integration via the ElementInternals API, accessibility features, and customizable styling. Automatically manages mutual exclusivity when radio buttons share the same name attribute.

Key Behavioral Model ​

Radio buttons implement mutually exclusive selection within groups:

  • Single selection: Checking one radio unchecks others in the same group

  • Click to select: Once selected, clicking again has no effect

  • Keyboard navigation: Space to check, Arrow keys to move between group members (when inside hy-radio-group)

  • No deselection: Users cannot uncheck a radio button directly (use setChecked(false) programmatically)

  • setChecked(): Programmatically select a radio and uncheck others in group

  • Group management: Automatic handling of name-based grouping

  • Form integration: Works seamlessly with native form submission via ElementInternals

Form Integration ​

This component implements the Form-Associated Custom Elements API. When used standalone (not inside hy-radio-group), each radio participates in form submission via ElementInternals. When used inside hy-radio-group, the group component handles form submission and individual radios become presentational.

Styling and Theming ​

The component uses design tokens from your design system:

  • small: 16px radio button, 8px inner dot

  • medium: 20px radio button, 12px inner dot (default)

  • large: 24px radio button, 16px inner dot

  • --hy-background-surface-base: Background color when unchecked

  • --hy-foreground-default-rest: Color of inner dot

  • --hy-foreground-default-rest: Label text color

  • --hy-foreground-subtle-rest: Top label and helper text color

  • --hy-foreground-danger-rest: Error message color

  • --hy-foreground-warning-rest: Warning message color

  • --hy-foreground-default-disabled: Disabled state color

  • Small: Uses --hy-body-sm-* tokens

  • Medium: Uses --hy-body-md-* tokens

  • Large: Uses --hy-body-lg-* tokens

When to use

  • One choice of a radio group.

Do

  • Keep the choices of a group parallel and mutually exclusive.

When not to use

  • A set of radios: put them in an hy-radio-group, which owns the name, the value, the label and the validation. Do not wire name by hand.
  • More than about six options: hy-select, or hy-combobox when people should type to filter.
  • People may pick more than one: hy-checkbox-group.
  • One yes/no choice: one hy-checkbox, or hy-switch when it applies at once.
  • An exclusive choice that changes a view at once (list or grid, day or week): hy-toggle-group.

Accessibility: built in ​

  • Full keyboard navigation support (Space to check, Arrow keys via hy-radio-group)
  • Proper ARIA attributes (role, aria-checked, aria-describedby)
  • Screen reader announcements for state changes
  • High contrast mode support with system color keywords
  • Minimum 48x48px touch targets on coarse-pointer devices
  • Semantic form integration with native validation via ElementInternals
  • Focus visible indicators (outline only on keyboard focus)

Accessibility: your part ​

  • Name every radio with label or default-slot text, or aria-label on the host. Never leave a bare dot.
  • Name the set with the hy-radio-group label, not with a loose heading above it.

Examples ​

Simple Single Selection (Standalone) ​

html
<form>
  <hy-radio name="size" value="small" label="Small"></hy-radio>
  <hy-radio name="size" value="medium" label="Medium" state="on"></hy-radio>
  <hy-radio name="size" value="large" label="Large"></hy-radio>
  <button type="submit">Submit</button>
</form>
html
<form>
  <hy-radio-group name="size" label="Select Size" value="medium">
    <hy-radio value="small" label="Small"></hy-radio>
    <hy-radio value="medium" label="Medium"></hy-radio>
    <hy-radio value="large" label="Large"></hy-radio>
  </hy-radio-group>
  <button type="submit">Submit</button>
</form>

Radio with Different Sizes ​

html
<hy-radio name="size-demo" value="small" label="Small Radio" size="small"> </hy-radio>
<hy-radio name="size-demo" value="medium" label="Medium Radio" size="medium"> </hy-radio>
<hy-radio name="size-demo" value="large" label="Large Radio" size="large"> </hy-radio>

Error and warning states ​

html
<hy-radio
  name="terms"
  value="agree"
  label="I agree to the terms and conditions"
  error-message="You must agree to continue"
  required
>
</hy-radio>
<hy-radio
  name="plan"
  value="legacy"
  label="Legacy plan"
  warning-message="This plan is retired at the end of the year"
>
</hy-radio>

Value states ​

One line shows under the radio: the error message, else the warning message, else the helper text. error paints the danger color and sets aria-invalid; warning paints the warning color and never does. Neither touches form validity. Inside hy-radio-group the group usually shows the message: a bare error or warning on a radio colors it without a line.

A top label and a helper line ​

html
<hy-radio
  name="plan"
  value="premium"
  label="Premium"
  top-label="Select plan"
  helper-text="Premium includes all features"
>
</hy-radio>

Design Token Requirements ​

This component requires the following design tokens to be defined:

Size tokens:

  • --hy-radio-indicator-size-sm, --hy-radio-indicator-size-md, --hy-radio-indicator-size-lg
  • --hy-radio-padding-form-sm, --hy-radio-padding-form-md, --hy-radio-padding-form-lg

Color tokens:

  • --hy-background-surface-base
  • --hy-foreground-default-rest, --hy-foreground-subtle-rest
  • --hy-foreground-danger-rest, --hy-foreground-default-disabled

Typography tokens (per size):

  • --hy-body-sm-*, --hy-body-md-*, --hy-body-lg-*
  • --hy-label-md-* (for the top-label), --hy-caption-md-* (for the helper text and messages)

API ​

Properties ​

PropertyAttributeTypeDefaultDescription
statestate'off' | 'on''off'The current state of the radio button: 'off' or 'on'.
labellabelstring''The text label displayed next to the radio button.
helperTexthelper-textstring''Helper text displayed below the radio button. An error or warning message replaces it.
topLabeltop-labelstring''Label displayed above the radio button, typically used for grouping.
disableddisabledbooleanfalseDisables the radio button, preventing user interaction.
requiredrequiredbooleanfalseMarks the radio button as required for form validation.
namenamestring''The name attribute for form submission and grouping radio buttons.
valuevaluestring''The value attribute for form submission.
errorerrorbooleanfalseError state: the value is not accepted. Paints the danger color and sets aria-invalid="true"; it never blocks submission. Set on its own it colors the radio and keeps the helper text — for a radio inside hy-radio-group, whose message the group shows. Setting error-message raises it.
errorMessageerror-messagestring''Error message shown under the radio (replaces the helper text). Setting it raises error, clearing it lowers it.
warningwarningbooleanfalseWarning state: the value is accepted but worth a second look. Paints the warning color and shows warning-message; never sets aria-invalid and never blocks submission. error wins when both are set.
warningMessagewarning-messagestring''Warning message shown under the radio (replaces the helper text). Setting it raises warning.
sizesize'small' | 'medium' | 'large''medium'The size of the radio button: 'small', 'medium' or 'large'.
ariaDescribedbyaria-describedbystring''Additional element IDs that describe the radio button for accessibility.
autofocusautofocusbooleanfalseMarks this radio as the auto-focus target. Read by hy-dialog (and other overlay containers) on show to choose initial focus. Also focuses on first connect when used outside an overlay, mirroring native HTML autofocus but routed through JS so SSR'd HTML doesn't pull focus during hydration.
radioIdradio-idstring''Id for the radio input element; empty uses the fixed input (components.md "Ids").
helperTextIdhelper-text-idstring''Id for the helper text element; empty uses the fixed helper.
topLabelIdtop-label-idstring''Id for the top label element; empty uses the fixed top-label.
checkedcheckedboolean—Whether the radio button is in the checked state (convenience property). Setting this uses setChecked() internally to properly manage form state.
form—HTMLFormElement | null—Returns the form element this radio button is associated with via ElementInternals. Only applicable when radio is standalone (not in radio-group).
validity—ValidityState—Returns the ValidityState object for this radio button via ElementInternals. Only applicable when radio is standalone (not in radio-group).
validationMessage—string—Returns the validation message for this radio button via ElementInternals. Only applicable when radio is standalone (not in radio-group).

Events ​

EventDetailDescription
change—Fired when state changes. Detail: { state: RadioState, checked: boolean, value: string, name: string, reason: 'user' }
input—Standard form input event for compatibility

CSS Parts ​

PartDescription
baseThe radio button's base wrapper element
helper-textThe helper text element
error-messageThe error message element
warning-messageThe warning message element
message-iconThe icon starting the error or warning message

CSS Custom Properties ​

The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.

PropertyDefaultDescription
--hy-radio-text-transformnoneText transform for radio label
--hy-radio-label-md-font-familyvar(--hy-label-md-font-family)Font family of the top label (the radio label reads the body role, the hint the caption role)
--hy-radio-border-default-restvar(--hy-border-default-rest)Ring color of the unchecked radio. (rest stays own-role).
--hy-radio-border-brand-restvar(--hy-border-brand-rest)Ring color of the checked radio. (checked escalates to brand).
--hy-radio-background-brand-indicator-restvar(--hy-background-brand-indicator-rest)Fill of the checked inner dot — a bare indicator (C6)
--hy-radio-background-danger-indicator-restvar(--hy-background-danger-indicator-rest)The checked dot in error state
--hy-radio-background-warning-indicator-restvar(--hy-background-warning-indicator-rest)The checked dot in warning state
--hy-radio-border-danger-restvar(--hy-border-danger-rest)Ring color in error state.
--hy-radio-border-warning-restvar(--hy-border-warning-rest)Ring color in warning state.
--hy-radio-foreground-danger-restvar(--hy-foreground-danger-rest)Error message text and icon color.
--hy-radio-foreground-warning-restvar(--hy-foreground-warning-rest)Warning message text and icon color.
--hy-radio-gap-layout-2xsvar(--hy-gap-layout-2xs)Gap between the message icon and its text.
--hy-radio-container-indicator-size-smvar(--hy-indicator-size-sm)Row floor (min-height) for the small size variant. The row is sized to its content: by default the floor is the size's indicator-size rung, so stacked radios and their top-label / helper text stay tight. The indicator is centered in the row. To line a radio up with an input in a form row, set this to a control height (e.g. var(--hy-control-height-sm)) per instance or per design system. Coarse pointers keep a touch-target floor.
--hy-radio-container-indicator-size-mdvar(--hy-indicator-size-md)Row floor (min-height) for the medium size variant.
--hy-radio-container-indicator-size-lgvar(--hy-indicator-size-lg)Row floor (min-height) for the large size variant.
--hy-radio-gap-form-smvar(--hy-gap-form-sm)The only spacing between the top-label, the row and the helper text or message.
--hy-radio-motion-transition-hover-durationvar(--hy-motion-transition-hover-duration)Transition duration for hover and focus state feedback
--hy-radio-motion-transition-hover-easingvar(--hy-motion-transition-hover-easing)Transition easing for hover and focus state feedback

Methods ​

checkValidity() ​

Checks the validity of the radio button against any validation constraints. Integrates with native HTML form validation via ElementInternals. Only applicable when radio is standalone (not in radio-group).

javascript
const radio = document.querySelector('hy-radio[required]');
if (!radio.checkValidity()) {
  console.log('Required radio button is not selected');
}

reportValidity() ​

Checks the validity of the radio button and shows validation messages if invalid. Triggers the browser's built-in validation UI via ElementInternals. Only applicable when radio is standalone (not in radio-group).

javascript
const radio = document.querySelector('hy-radio[required]');
radio.reportValidity(); // Shows native validation message if invalid

focus() ​

Sets focus on the radio button input element.

javascript
const radio = document.querySelector('hy-radio');
radio.focus(); // Focuses the radio button for keyboard interaction

blur() ​

Removes focus from the radio button input element.

javascript
const radio = document.querySelector('hy-radio');
radio.blur(); // Removes focus from the radio button

setChecked() ​

Programmatically sets the checked state of the radio button. When setting to true and radio is standalone (not in radio-group), automatically unchecks other radio buttons in the same group and updates the form value via ElementInternals. When setting to false, clears the form value.

Parameters:

  • checked - Whether the radio button should be checked
javascript
const radio = document.querySelector('hy-radio');
radio.setChecked(true); // Check this radio and uncheck others in group
radio.setChecked(false); // Uncheck this radio

Built with Lit. Documented with VitePress.