Skip to content

HyRadioGroup ​

Usage examples on this page are written for Lit / plain HTML (<hy-radio-group>). The same component ships as HyRadioGroup 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 container component that manages a group of radio buttons as a cohesive unit. Provides centralized state management, keyboard navigation, accessibility features, and form integration via ElementInternals API. Automatically coordinates selection states and ensures only one radio button is selected at a time.

Form Integration ​

This component implements the Form-Associated Custom Elements API, making it the primary form control for the group. Individual hy-radio components within the group become presentational and do not participate in form submission - the group handles all form integration.

Validation ​

The component supports native HTML form validation through the required attribute. The validation is enforced during form submission and can be checked programmatically via checkValidity() and reportValidity(). The aria-required attribute is set for assistive technologies. A required group draws the required marker (*) after its label, as every field does.

Disabled State ​

When disabled, the radio group:

  • Shows a not-allowed cursor across the entire component area
  • Prevents all interactions via pointer-events: none on the radio list
  • Applies --hy-foreground-default-disabled color token to labels and helper text
  • Delegates visual disabled styling (opacity, colors) to child radio buttons

Keyboard Navigation ​

The radio group implements the ARIA radio group pattern with full keyboard support:

  • Tab: The group is one Tab stop — the checked radio, else the first enabled one
  • Arrow Right/Down: Move to next radio and select it (wraps to first)
  • Arrow Left/Up: Move to previous radio and select it (wraps to last)
  • Home: Move to first radio and select it
  • End: Move to last radio and select it
  • Space: Select the currently focused radio (if not already selected)

Note: Unlike tab groups, all four arrow keys work in both horizontal and vertical orientations per the WAI-ARIA specification. Arrow navigation automatically selects the newly focused radio button and skips disabled radios.

When to use

  • Choosing exactly one option from two to six that people should compare: a plan, a shipping method, a format.
  • A choice where seeing every option at once helps the decision.

Do

  • Order the options in a meaningful way: by size, by frequency or as a sequence.

When not to use

  • Options people can pick several of: hy-checkbox-group.
  • One yes / no choice: a single hy-checkbox, or hy-switch when it takes effect at once. Two radios for yes and no add nothing.
  • More than about six options: hy-select, or hy-combobox when people search them.
  • A choice that changes the view at once, such as sort order or list / grid: hy-toggle-group.

Choice cards ​

The items can be cards instead of radios: a hy-card with a value inside the group is a choice card — it draws its own stretched native radio, so a click anywhere on it picks it, the arrows move between the cards, and the group owns the value, the form entry, the message line and required as with radios. A group holds radios or cards, not both.

Accessibility: your part ​

  • When a heading or dialog title already asks the question, name the group with aria-labelledby pointing at it instead of repeating it in label.
  • Give every hy-radio its own label that reads as an answer on its own; name a choice card from its own heading (aria-labelledby) or aria-label.

Examples ​

Basic usage ​

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>

Horizontal layout ​

html
<hy-radio-group
  name="layout"
  label="Choose layout direction"
  orientation="horizontal"
  value="horizontal"
>
  <hy-radio value="horizontal" label="Horizontal"></hy-radio>
  <hy-radio value="vertical" label="Vertical"></hy-radio>
</hy-radio-group>

With validation ​

html
<hy-radio-group
  name="plan"
  label="Subscription Plan"
  helper-text="Choose the plan that best fits your needs"
  required
>
  <hy-radio value="free" label="Free" helper-text="Basic features"></hy-radio>
  <hy-radio value="pro" label="Pro ($9.99/month)" helper-text="Advanced features"></hy-radio>
  <hy-radio value="enterprise" label="Enterprise" helper-text="Full feature set"></hy-radio>
</hy-radio-group>

Error and warning ​

html
<!-- Not accepted: danger color, aria-invalid, announced as an alert -->
<hy-radio-group name="plan" label="Plan" error-message="Choose a plan to continue">
  <hy-radio value="free" label="Free"></hy-radio>
  <hy-radio value="pro" label="Pro"></hy-radio>
</hy-radio-group>

<!-- Accepted, but worth a second look: warning color, announced politely,
never invalid and never blocks submission -->
<hy-radio-group
  name="shipping"
  label="Shipping"
  value="overnight"
  warning-message="Overnight costs three times more"
>
  <hy-radio value="standard" label="Standard"></hy-radio>
  <hy-radio value="overnight" label="Overnight"></hy-radio>
</hy-radio-group>

Disabled group ​

html
<hy-radio-group
  name="notification"
  label="Notification Settings"
  disabled
  value="email"
  helper-text="Settings are currently locked"
>
  <hy-radio value="email" label="Email only"></hy-radio>
  <hy-radio value="sms" label="SMS only"></hy-radio>
  <hy-radio value="both" label="Email and SMS"></hy-radio>
</hy-radio-group>
<!-- When disabled: cursor shows not-allowed, all interactions blocked,
text colors use --hy-foreground-default-disabled token -->

Complete form integration ​

html
<form>
  <hy-radio-group name="payment" label="Payment Method" required>
    <hy-radio value="credit" label="Credit Card"></hy-radio>
    <hy-radio value="debit" label="Debit Card"></hy-radio>
    <hy-radio value="paypal" label="PayPal"></hy-radio>
  </hy-radio-group>

  <hy-radio-group name="shipping" label="Shipping Speed" value="standard">
    <hy-radio value="standard" label="Standard (5-7 days)"></hy-radio>
    <hy-radio value="express" label="Express (2-3 days)"></hy-radio>
    <hy-radio value="overnight" label="Overnight"></hy-radio>
  </hy-radio-group>
  <button type="submit">Submit</button>
</form>

Programmatic Methods ​

javascript
const group = document.querySelector('hy-radio-group');

// Set value programmatically
group.value = 'medium';

// Get current value
console.log(group.value); // 'medium'

// Form validation
group.checkValidity();
group.reportValidity();

// Focus management
group.focus(); // Focuses selected or first radio

API ​

Properties ​

PropertyAttributeTypeDefaultDescription
labellabelstring''The label displayed above the radio group.
helperTexthelper-textstring''Helper text displayed below the radio group to provide additional context. Replaced by error-message / warning-message while one of them shows.
namenamestring''The name attribute for form submission.
valuevaluestring''The currently selected value in the radio group.
orientationorientation'horizontal' | 'vertical''vertical'The visual layout of the radio buttons: 'horizontal' or 'vertical'. horizontal collapses back to vertical below a 30rem container width via a container query. Being an inline-size query container, the group fills its container's inline size (inline-size: 100%) instead of sizing to its content — in a flex/grid parent, constrain the parent (or the group) rather than expecting shrink-wrap.
sizesize'small' | 'medium' | 'large''medium'The size of the radio buttons in the group. Set on every radio, over its own size.
disableddisabledbooleanfalseDisables the entire radio group and all radio buttons within it. When disabled: - Cursor changes to not-allowed across the entire component - All interactions are prevented via pointer-events: none - Labels and message lines use --hy-foreground-default-disabled color token - Child radio buttons apply their own disabled visual styling
requiredrequiredbooleanfalseMarks the radio group as required for form validation: aria-required is set for assistive technologies, and the required marker (*) follows the label.
errorerrorbooleanfalseError state: the selection is not accepted and has to change. Sets aria-invalid on the radio group and shows error-message in the danger color. Visual and for screen readers only — form validity comes from required and setCustomValidity().
errorMessageerror-messagestring''Error message shown below the radio list (replaces the helper text). Setting it raises error; clearing it lowers it.
warningwarningbooleanfalseWarning state: the selection is accepted but worth a second look. Shows warning-message in the warning color; never sets aria-invalid and never touches form validity. error wins when both are set.
warningMessagewarning-messagestring''Warning message shown below the radio list (replaces the helper text). Setting it raises warning; clearing it lowers it.
layoutlayout'block' | 'inline''block'Row layout: block (default) stacks label above the radio list; inline puts label on the left and list on the right. Helper/error always sit under the list (col 2–end), never under the label. Set --hy-radio-group-label-column-width on a wrapping container so stacked groups align.
ariaDescribedbyaria-describedbystring''Additional element IDs that describe the radio group for accessibility.
form—HTMLFormElement | null—Returns the form element this radio group is associated with via ElementInternals.
validity—ValidityState—Returns the ValidityState object for this radio group via ElementInternals.
validationMessage—string—Returns the validation message for this radio group via ElementInternals.

Events ​

EventDetailDescription
change—Fired when the selected radio button changes. Detail: { value: string, name: string, reason: StateChangeReason }
input—Standard form input event for compatibility

Slots ​

SlotDescription
defaultThe radio buttons to include in this group

CSS Parts ​

PartDescription
baseThe radio group's base wrapper element
labelThe group label element
radio-listThe container for the radio buttons
helper-textThe helper text element
error-messageThe error message element (shown while error is set)
warning-messageThe warning message element (shown while warning is set and error is not)
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-group-foreground-danger-hovervar(--hy-foreground-danger-hover)Required marker (*) color.
--hy-radio-group-foreground-danger-restvar(--hy-foreground-danger-rest)Error message text and icon color.
--hy-radio-group-foreground-warning-restvar(--hy-foreground-warning-rest)Warning message text and icon color.
--hy-radio-group-gap-layout-2xsvar(--hy-gap-layout-2xs)Gap between the message icon and its text.
--hy-radio-group-label-column-width—Width of the label column in layout="inline" mode. No default — set on a wrapping container so stacked groups align.
--hy-radio-group-field-gap-form-smvar(--hy-gap-form-sm)Gap between the group's parts: the label, the radio list and the helper / error text.
--hy-radio-group-stack-gap-form-smvar(--hy-gap-form-sm)Gap between stacked radios in the vertical orientation. The radio rows are content-sized, so the group owns the rhythm.

Methods ​

checkValidity() ​

Checks the validity of the radio group against validation constraints. Integrates with native HTML form validation via ElementInternals.

javascript
const group = document.querySelector('hy-radio-group[required]');
if (!group.checkValidity()) {
  console.log('Required radio group has no selection');
}

reportValidity() ​

Checks the validity of the radio group and shows validation messages if invalid. Triggers the browser's built-in validation UI via ElementInternals.

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

setCustomValidity() ​

Sets a custom validation message for the radio group.

Parameters:

  • message - The custom validation message
javascript
const group = document.querySelector('hy-radio-group');
group.setCustomValidity('Please select a premium option');

focus() ​

Sets focus on the group's Tab stop: the selected radio, or the first enabled radio if none is selected (or the selected one is disabled). A disabled radio is never focused.

javascript
const radioGroup = document.querySelector('hy-radio-group');
radioGroup.focus(); // Focus the selected or first enabled radio button

Built with Lit. Documented with VitePress.