HyRadio
Usage examples on this page are written for Lit / plain HTML (
<hy-radio>). The same component ships asHyRadioin@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 colorSmall: Uses
--hy-body-sm-*tokensMedium: Uses
--hy-body-md-*tokensLarge: 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 wirenameby hand. - More than about six options:
hy-select, orhy-comboboxwhen people should type to filter. - People may pick more than one:
hy-checkbox-group. - One yes/no choice: one
hy-checkbox, orhy-switchwhen 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
labelor default-slot text, oraria-labelon the host. Never leave a bare dot. - Name the set with the
hy-radio-grouplabel, not with a loose heading above it.
Examples
Simple Single Selection (Standalone)
<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>Using with Radio Group (Recommended)
<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
<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
<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
<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
state | state | 'off' | 'on' | 'off' | The current state of the radio button: 'off' or 'on'. |
label | label | string | '' | The text label displayed next to the radio button. |
helperText | helper-text | string | '' | Helper text displayed below the radio button. An error or warning message replaces it. |
topLabel | top-label | string | '' | Label displayed above the radio button, typically used for grouping. |
disabled | disabled | boolean | false | Disables the radio button, preventing user interaction. |
required | required | boolean | false | Marks the radio button as required for form validation. |
name | name | string | '' | The name attribute for form submission and grouping radio buttons. |
value | value | string | '' | The value attribute for form submission. |
error | error | boolean | false | Error 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. |
errorMessage | error-message | string | '' | Error message shown under the radio (replaces the helper text). Setting it raises error, clearing it lowers it. |
warning | warning | boolean | false | Warning 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. |
warningMessage | warning-message | string | '' | Warning message shown under the radio (replaces the helper text). Setting it raises warning. |
size | size | 'small' | 'medium' | 'large' | 'medium' | The size of the radio button: 'small', 'medium' or 'large'. |
ariaDescribedby | aria-describedby | string | '' | Additional element IDs that describe the radio button for accessibility. |
autofocus | autofocus | boolean | false | Marks 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. |
radioId | radio-id | string | '' | Id for the radio input element; empty uses the fixed input (components.md "Ids"). |
helperTextId | helper-text-id | string | '' | Id for the helper text element; empty uses the fixed helper. |
topLabelId | top-label-id | string | '' | Id for the top label element; empty uses the fixed top-label. |
checked | checked | boolean | — | 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
| Event | Detail | Description |
|---|---|---|
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
| Part | Description |
|---|---|
base | The radio button's base wrapper element |
helper-text | The helper text element |
error-message | The error message element |
warning-message | The warning message element |
message-icon | The 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.
| Property | Default | Description |
|---|---|---|
--hy-radio-text-transform | none | Text transform for radio label |
--hy-radio-label-md-font-family | var(--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-rest | var(--hy-border-default-rest) | Ring color of the unchecked radio. (rest stays own-role). |
--hy-radio-border-brand-rest | var(--hy-border-brand-rest) | Ring color of the checked radio. (checked escalates to brand). |
--hy-radio-background-brand-indicator-rest | var(--hy-background-brand-indicator-rest) | Fill of the checked inner dot — a bare indicator (C6) |
--hy-radio-background-danger-indicator-rest | var(--hy-background-danger-indicator-rest) | The checked dot in error state |
--hy-radio-background-warning-indicator-rest | var(--hy-background-warning-indicator-rest) | The checked dot in warning state |
--hy-radio-border-danger-rest | var(--hy-border-danger-rest) | Ring color in error state. |
--hy-radio-border-warning-rest | var(--hy-border-warning-rest) | Ring color in warning state. |
--hy-radio-foreground-danger-rest | var(--hy-foreground-danger-rest) | Error message text and icon color. |
--hy-radio-foreground-warning-rest | var(--hy-foreground-warning-rest) | Warning message text and icon color. |
--hy-radio-gap-layout-2xs | var(--hy-gap-layout-2xs) | Gap between the message icon and its text. |
--hy-radio-container-indicator-size-sm | var(--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-md | var(--hy-indicator-size-md) | Row floor (min-height) for the medium size variant. |
--hy-radio-container-indicator-size-lg | var(--hy-indicator-size-lg) | Row floor (min-height) for the large size variant. |
--hy-radio-gap-form-sm | var(--hy-gap-form-sm) | The only spacing between the top-label, the row and the helper text or message. |
--hy-radio-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-radio-motion-transition-hover-easing | var(--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).
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).
const radio = document.querySelector('hy-radio[required]');
radio.reportValidity(); // Shows native validation message if invalidfocus()
Sets focus on the radio button input element.
const radio = document.querySelector('hy-radio');
radio.focus(); // Focuses the radio button for keyboard interactionblur()
Removes focus from the radio button input element.
const radio = document.querySelector('hy-radio');
radio.blur(); // Removes focus from the radio buttonsetChecked()
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
const radio = document.querySelector('hy-radio');
radio.setChecked(true); // Check this radio and uncheck others in group
radio.setChecked(false); // Uncheck this radio