Selection groups
Which of radio, checkbox and switch groups to use, how to lay out a group, and how to mark required questions.
A selection group lets a person choose among a fixed set of options. Spark has
three: RadioGroup (exactly one), CheckboxGroup (any number, saved together) and
SwitchGroup (any number, each applied at once). All three take the same
listStyle and the same SelectionItem rows, so they look alike in a form.
import { CheckboxGroup } from '@epilot/spark-ui/checkbox-group'
import { Checkbox } from '@epilot/spark-ui/checkbox'
import {
FieldDescription,
FieldError,
FieldLegend,
FieldSet,
} from '@epilot/spark-ui/field'
import { SelectionItem } from '@epilot/spark-ui/selection-item'Which group when
| Need | Use |
|---|---|
| Exactly one option, saved with the form | RadioGroup |
| Any number of options, saved with the form | CheckboxGroup |
| Any number of settings, each takes effect when flipped | SwitchGroup |
| One option, shown as large cards | RadioCardGroup |
| One option from a short list of views | SegmentedControl |
A single yes/no question is a lone Checkbox or Switch, not a group. Use a switch
when the change applies immediately, a checkbox when the person confirms with a
button.
Slot order
Build every group the same way, from top to bottom:
FieldLegend: the question.FieldDescription: the hint.FieldError: shown only while the group is invalid.- The options.
<FieldSet indicator="required">
<FieldLegend>Notify me by</FieldLegend>
<FieldDescription>Pick any.</FieldDescription>
{invalid && <FieldError>Choose at least one channel.</FieldError>}
<CheckboxGroup listStyle="surface" value={value} onValueChange={setValue}>
<SelectionItem
control={<Checkbox value="email" />}
label="Email"
description="Invoices and reminders."
/>
</CheckboxGroup>
</FieldSet>The error renders as a callout between the hint and the options, so the hint stays readable and the error sits next to what it describes.
List styles
listStyle reuses the input chrome (soft, surface, outlined, ghost), so a
group matches the inputs around it. Use the switcher above to compare them. Without
listStyle the group is a plain stack of controls, as before. SelectionItem is the
row that holds the control, label and description; descriptions work with or without
listStyle.
Required and optional
- Use one marking convention per form. Do not mix
*and "(optional)". - Mark the minority: when most questions are required, mark the optional ones with
indicator="optional"; when most are optional, mark the required ones withindicator="required". - When you use
*, add a "* Required" key line to the form so the symbol is explained. - A mark on single inputs is not part of Spark yet;
indicatorapplies to aFieldSetlegend.
Error state
Give the FieldError inside the set. The legend turns red, the callout appears above
the options and the list takes the error tint. Submit the form below without choosing
an option.
Accessibility
- Every group is named by its
FieldLegendthrough the<fieldset>. Do not leave a group without a legend oraria-label. - The
<fieldset>lists the error, then the hint, inaria-describedby. The error also hasrole="alert", so it is read when it appears. indicator="required"adds hidden "required" text next to the visible*.indicator="optional"is visible text. Translate either withindicatorLabel.- Each
SelectionItemnames its control by the label and describes it by the description, so a screen reader reads the two apart. - Color is never the only signal for an error: the callout states the problem in words.