Skip to content
Components, switch product

Radio group

<gui-radiogroup> is a group of radio buttons to pick one option out of a few. It renders native radio buttons inside an element with the radiogroup role, named by the label.

Code of the example above
RadioGroupBasic.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const options = ['Standard', 'Express', 'Overnight'];
export function RadioGroupBasic() {
return <GuiRadiogroup label="Delivery" options={options} />;
}

The element’s entry point is @golemui/gui-components/radiogroup, which registers <gui-radiogroup> and nothing else. To install the package and load the stylesheet, see Installation.

The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.

DeliveryField.tsx
import { useState } from 'react';
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const speeds = ['Standard', 'Express', 'Overnight'];
export function DeliveryField() {
const [delivery, setDelivery] = useState('');
return (
<GuiRadiogroup
label="Delivery"
name="delivery"
options={speeds}
value={delivery}
onGuiChange={(event) => setDelivery(event.detail.value ?? '')}
/>
);
}

In HTML, options can also be a JSON attribute, as in the examples below.

Properties

Property Type Default Description
localeId attribute locale-id string 'en' BCP 47 locale for formatting and parsing, such as en-US or es.
value OptionValue – The value of the selected option.
options OptionInput[] [] The options: values, which are both the text and the value, { label, value } objects, or any objects read through label-field and value-field.
labelField attribute label-field string – For options given as objects, the key of the text to show.
valueField attribute value-field string – For options given as objects, the key of the value.
direction 'row' | 'column' 'column' Lays the options out in a row or a column.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

Form control properties

Every field has these, from the base class of the form controls.

Property Type Default Description
name string – The name the value is submitted under. Without it the element stays out of its form. Reflected, like a native control's: the form reads a single value's name from the attribute.
label string – The visible label, which also names the control for assistive technology. Without one the control has no accessible name; the date, time, range, tags and file fields warn about it in development builds.
hint string – Help text shown under the label and announced as the control's description.
errors string[] [] Error messages to show, typically from your own validation. They show as soon as they are set, unless touched is false.
touched boolean – Whether the user has interacted with the control. Leave it unset unless you validate on interaction: false holds errors back until it becomes true.
required boolean false The control needs a value: it is marked as required and, with a name, blocks submission.
readOnly attribute readonly boolean – The value can be read and focused but not changed.
disabled boolean – Disabled by its own attribute or by a disabled <fieldset> around it. Setting the property sets the attribute, like a native control's.
form read-only, no attribute HTMLFormElement | null – The <form> the element belongs to, if any.
validity read-only, no attribute ValidityState – The element's validity, like a native control's.
validationMessage read-only, no attribute string – The message the browser shows for an invalid value.

Methods

Method Description
checkValidity(): boolean –
reportValidity(): boolean –

Events

Every event bubbles and is composed, so you can listen on any ancestor.

Event Description
gui-input The user changed the value. detail.value is the new value.
gui-change The user committed the value. detail.value is the committed value.
gui-blur Focus left the control.

options takes the list of options. As an attribute it is a JSON array; from JavaScript, set the options property to an array. Each option is a string or a number, which is both its text and its value, as above, or a { label, value } object, to show a text other than the value.

value is the value of the selected option. Leave it unset to start with none selected.

Code of the example above
RadioGroupObjects.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const plans = [
{ label: 'Free, for one person', value: 'free' },
{ label: 'Team, up to 20 people', value: 'team' },
{ label: 'Enterprise, with single sign-on', value: 'enterprise' },
];
export function RadioGroupObjects() {
return <GuiRadiogroup label="Plan" value="team" options={plans} />;
}

When your options are objects with other keys, such as records from an API, label-field names the key of the text and value-field the key of the value. You don’t need to map them first.

Code of the example above
RadioGroupFields.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const airports = [
{ city: 'Lisbon', code: 'LIS' },
{ city: 'Porto', code: 'OPO' },
{ city: 'Faro', code: 'FAO' },
];
export function RadioGroupFields() {
return (
<GuiRadiogroup
label="Departure airport"
labelField="city"
valueField="code"
options={airports}
/>
);
}

Option values can be numbers. An attribute is always text, though, so value="2" selects no number option: set the value property instead, as here. The value the element reports keeps the option’s type.

Code of the example above
RadioGroupNumbers.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const seats = [1, 2, 4, 8];
export function RadioGroupNumbers() {
return (
<GuiRadiogroup
id="seats-radio"
label="Seats"
direction="row"
options={seats}
value={2}
/>
);
}

The options stack in a column. direction="row" lays them out side by side, for a few short options. Inside an element with the gui-container class, a row stacks back into a column below 480px wide: see Grid.

Code of the example above
RadioGroupRow.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const sizes = ['S', 'M', 'L', 'XL'];
export function RadioGroupRow() {
return (
<GuiRadiogroup label="Size" direction="row" value="M" options={sizes} />
);
}

A disabled group can’t be focused or changed, and a form doesn’t submit it. A readonly group can be focused, but clicks and the arrow keys don’t change the selected option, and a form submits its value.

Code of the example above
RadioGroupDisabledReadonly.tsx
import { GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function RadioGroupDisabledReadonly() {
return (
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell">
<GuiRadiogroup
label="Billing"
value="Yearly"
options={['Monthly', 'Yearly']}
disabled
/>
</div>
<div className="gui-grid__cell">
<GuiRadiogroup
label="Region"
value="Europe"
options={['Europe', 'Americas']}
readOnly
/>
</div>
</div>
);
}

With a name, <gui-radiogroup> submits the value of the selected option under that name, and nothing while none is selected. required blocks the submit until the user picks one. Submit the form below, then pick an option and submit again.

Code of the example above
RadioGroupNativeForm.tsx
import { useState, type SubmitEvent } from 'react';
import { GuiButton, GuiRadiogroup } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const sources = [
{ label: 'A friend', value: 'friend' },
{ label: 'A search engine', value: 'search' },
{ label: 'A conference talk', value: 'talk' },
];
export function RadioGroupNativeForm() {
const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
setResult(`Submitted ${JSON.stringify(data)}`);
};
return (
<form id="survey" onSubmit={onSubmit}>
<div className="gui-grid">
<div className="gui-grid__cell">
<GuiRadiogroup
label="How did you hear about us?"
name="source"
required
options={sources}
/>
</div>
<div className="gui-grid__cell">
<GuiButton type="submit" label="Send" />
</div>
</div>
<p id="survey-result" role="status">
{result}
</p>
</form>
);
}

The radio buttons inside stay out of the form: the element submits the value itself.

How the elements submit, validate and reset is in Native forms.

<gui-radiogroup> renders into the light DOM, so you can style every part with plain CSS. These are the parts and their public classes:

gui-radiogroup
<gui-radiogroup class="gui-field">
<span class="gui-label" id="delivery_label">
<span class="gui-label__text">
Delivery<span aria-hidden="true"> *</span>
</span>
<div class="gui-widget-hint" id="delivery_hint" aria-hidden="true">
Express arrives in two days.
</div>
</span>
<div
role="radiogroup"
class="gui-widget"
id="delivery"
aria-labelledby="delivery_label"
aria-describedby="delivery_hint"
aria-required="true"
aria-invalid="true"
aria-errormessage="delivery_errors"
>
<label for="delivery_0">
<input
type="radio"
tabindex="-1"
id="delivery_0"
name="delivery"
form="delivery_none"
value="Standard"
aria-describedby="delivery_hint"
aria-invalid="true"
aria-errormessage="delivery_errors"
aria-required="true"
/>
Standard
</label>
<label for="delivery_1">
<input
type="radio"
tabindex="0"
id="delivery_1"
name="delivery"
form="delivery_none"
value="Express"
aria-describedby="delivery_hint"
aria-invalid="true"
aria-errormessage="delivery_errors"
aria-required="true"
/>
Express
</label>
<!-- One label and radio per option. -->
</div>
<div class="gui-validator" id="delivery_errors" role="alert">
<div class="gui-validator__error">Pick a delivery option.</div>
</div>
</gui-radiogroup>
ClassPart
gui-fieldThe element itself. Lines its parts up with the fields next to it in a gui-grid row.
gui-labelThe group’s label, with the text and the hint.
gui-label__textThe label text and the required marker.
gui-widget-hintThe hint.
gui-widgetThe group, with a <label> and a radio button per option.
gui-widget--horizontalOn the group with direction="row": lays the options out in a row.
gui-validatorThe errors container. It is always there, so the alert role announces new errors.
gui-validator__errorOne error message.

The options have no class of their own: select them as gui-radiogroup label and gui-radiogroup input. The ids derive from uid, or from a generated id such as gui-radiogroup-3 when you don’t set one. Select by class or by tag, not by id.

The group reads these design tokens. Set them on :root to change every group, or on any element to change the groups inside it:

TokenWhat it styles
--gui-bg-surfaceThe radio buttons’ background.
--gui-border-defaultThe radio buttons’ border.
--gui-intent-primaryThe dot and the border of the selected radio button.
--gui-text-defaultThe label and the options’ text.
--gui-font-familyThe font.
--gui-font-smThe text size of the label and the options.
--gui-font-xsThe text size of the hint and the errors.
--gui-text-secondaryThe hint.
--gui-border-focusThe border of the focused radio button.
--gui-shadow-focusThe focus ring.
--gui-intent-errorThe radio buttons’ border and the messages when there are errors.
--gui-shadow-focus-errorThe focus ring when there are errors.
--gui-space-1The space between the label, hint and errors.
styles.css
/* More room between the options of every group. */
gui-radiogroup .gui-widget {
row-gap: 0.75rem;
}

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.

  • Group. The options sit in an element with the radiogroup role, named by the label through aria-labelledby. Without a label the group has no accessible name. Each radio button is named by its option’s text.
  • Description. The hint is the group’s description, through aria-describedby.
  • Errors. While errors show, the group and its radio buttons have aria-invalid="true" and point at them with aria-errormessage, and screen readers announce new errors as they appear.
  • Required and read-only. required sets aria-required, and readonly sets aria-readonly on the group, which radio buttons can’t carry themselves.
  • One tab stop. The group is a single stop in the tab order: Tab lands on the selected option, or on the first one when none is selected.
KeyAction
TabMoves focus into the group, to the selected option, and out of it.
↓ →Selects the next option, and fires gui-change.
↑ ←Selects the previous option, and fires gui-change.
SpaceSelects the focused option, when none is selected yet.

In a read-only group the arrow keys and Space do nothing.

What every element shares, such as translating the browser messages, is in Accessibility.