Several radios, checkboxes or choice cards under one question: a fieldset with its legend, help, and one error message for the group.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
layoutsmbk-field-group--layout-<value> | verticalhorizontal | vertical | Vertical stacks the options. Horizontal sets them in a row that wraps, only for a few short labels without lines under them. |
errorsmbk-field-group--error | On or off | Off | The answer is invalid, such as a required question left empty. Every option takes its error look and one message under the options says what to do. |
disabledsmbk-field-group--disabled | On or off | Off | Every option is disabled; the question and its help stay readable. |
Examples
Usage
A question over radios, each with a line under its label. Tab reaches the group once; the arrow keys move the choice.
Checkboxes
Checkboxes when several can be picked: each is its own Tab stop.
Horizontal
A row for two or three short labels with nothing under them.
Required
The red mark says the question must be answered; the radios are marked required.
Error
Every option takes its error look and one message under the options says what to do. A screen reader reads it as it appears.
Disabled
Every option dims; the question and its help stay readable.
Guidelines
Purpose
Several radios, checkboxes or choice cards under one question. It is a fieldset whose legend asks the question, so a screen reader names the group before its first choice, with help under the question and one error message for the whole group. It is to a set of choices what the field is to one control.
When to use
- One question answered by picking one of a few choices (radios) or any of them (checkboxes).
- A few choices that each need a line of explanation, as choice cards.
When not to use
- A single control. Use the field.
- More than about five choices. Use the select.
- A choice that takes effect at once with two states. Use the switch or the segmented control.
Usage rules
Do
- Ask a question in the legend, and keep the option labels short.
- Pre-select the most common radio when there is one.
Don't
- Mix radios, checkboxes and cards in one group.
- Lay long labels or options with lines under them out in a row.
Ask the question in the legend, so a screen reader names the group before its first option.
A row of options with no question leaves the reader to guess what they choose.
Keep a row to a few short labels; give options with a line under them their own rows.
Long labels with lines under them, side by side, wrap into a ragged block that is hard to scan.
Anatomy
- The block, a fieldset with no edge of its own.
- The legend part, the question: the label component in its strong tone, with the required mark when an answer is needed.
- The description part, optional help under the question: a field message of the help type. A line of words, or blocks such as paragraphs, a list or code.
- The options part, the choices: two to five radios, checkboxes or choice cards, all of one kind.
- The option part, given to each radio or checkbox in the group: its label a step larger than a lone control's, its mark a wider step away, and an optional line under the label.
- The message part, in error, under the options: a field message of the error type.
Behavior
The radios, checkboxes and choice cards inside take the group's name, its error and its required state, so each is written once, on the group. The radios share one name, the page's or one the group makes, so they act as one choice.
The group can hold which radio is chosen, as a select holds its option. Give it value and follow it with onChange, which is told the value of each radio chosen; or give it defaultValue and the group keeps the choice itself. Each radio, or radio choice card, is checked when its own value is the group's, and an empty value checks none. Without either, each radio keeps its own state, and onChange still hears every choice. Checkboxes keep their own state in either case.
The help under the question is a line of words, or blocks: paragraphs, a list, code, as a question written in Markdown has. Given words alone it is a paragraph; given anything else it is a div, which can hold blocks. Its paragraphs sit a paragraph apart, its list's bullets inside the group's edge, and its code in the small code style. Either way it describes the group, so a screen reader reads all of it with the choice.
Two layouts. Vertical stacks the options at the medium stack step. Horizontal sets them in a row that wraps, for two or three short labels with nothing under them.
In error, every option takes its error look and the message under the options says what to do. Given error with no message, the help text moves there as the message. Disabled, every option dims and is out of reach, while the question and its help stay readable; one option can also be disabled alone.
Accessibility
What it does for you
- The legend names the group: a screen reader says the question when focus moves into its first choice.
- The help and the message describe the group through
aria-describedby, and the message is an alert, read as it appears. - Radios take one stop for the Tab key, and the arrow keys move the choice; each checkbox is its own stop, and Space ticks it.
- Required puts the mark on the question and marks each radio required.
- In error, each option is marked invalid, and the message says what to change in words, with an icon.
What you must do
- Write the legend as the question, so it is what a screen reader says on entering the group.
- For "at least one checkbox", check in the page and show the message: it has no native form.
- In error, write the message as what to change.
- Written by hand, help that holds blocks is a div with the field message's classes, its words in a div of the text part, since a paragraph cannot hold a list.
How to test
- Tab into the group: for radios, focus lands on the chosen radio, or the first, and the arrow keys move the choice. Each checkbox is its own stop, and Space ticks it.
- With a screen reader, entering the group says the question, and the help and message are read with the choice.
- In error, the message is read as it appears and each option is read as invalid.
- With text at 200% and the page 320 pixels wide, the legend and options stay whole.
- In a forced-color mode, the options and the error icon are still visible.
Keyboard
| Key | What it does |
|---|---|
| TaborShift+Tab | Moves focus into and out of the group: one stop for all its radios, one stop for each checkbox. |
| Up ArroworDown ArroworLeft ArroworRight Arrow | Among radios, moves focus to the neighboring radio and chooses it. |
| Space | On a checkbox, ticks or unticks it. |
Source
The files the field group is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-field-group — Field group. Figma "Field group" 7101:469 (State: Default, Disabled, Error × Layout: Vertical, Horizontal).
* Several radios, checkboxes or choice cards under one question: a fieldset
* whose legend is the label component in its strong tone, an optional line of
* help under it, the options, and one field message for the whole group in
* error. Each radio or checkbox in it is Figma's Choice option (7101:128): the
* body text a step larger than a lone control's, a wider gap to its mark, and
* an optional line under the label. Disabled, the fieldset disables every
* option; the legend and the help keep their colors, as Figma draws them. */
.smbk-field-group {
box-sizing: border-box;
display: flex;
flex-direction: column;
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
/* Floated, the legend is a row like the others, not drawn in the fieldset's edge; it still names the group. */
.smbk-field-group__legend {
float: inline-start;
padding: 0;
}
/* The question and its help sit together; the options and the message follow at the medium stack step. */
.smbk-field-group > :not(:first-child) {
margin-block-start: var(--smbk-space-stack-md);
}
.smbk-field-group > .smbk-field-group__legend + .smbk-field-group__description {
margin-block-start: var(--smbk-space-text-label);
}
.smbk-field-group__options {
display: flex;
flex-direction: column;
gap: var(--smbk-space-stack-md);
}
/* Horizontal: the options hug their labels and wrap; only for a few short labels without lines under them. */
.smbk-field-group--layout-horizontal .smbk-field-group__options {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
column-gap: var(--smbk-space-inline-2xl);
row-gap: var(--smbk-space-stack-md);
}
/* Figma's Choice option: a radio or a checkbox with its label in Body MD and the mark a medium inline step
away. Two classes, so it wins over the control's own row whichever stylesheet loads first. */
.smbk-field-group__option:is(.smbk-radio, .smbk-checkbox) {
gap: var(--smbk-space-inline-md);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}
/* The help under the question holds a line of words, or blocks: paragraphs a paragraph apart, a list's items a line
of list spacing apart with the bullets inside the group's edge, and code in the small code style, wrapping rather
than running past the group. The message under the options takes the same blocks. */
:is(.smbk-field-group__description, .smbk-field-group__message) .smbk-field-message__text > :where(p, ul, ol, pre, blockquote) {
margin: 0;
}
:is(.smbk-field-group__description, .smbk-field-group__message) .smbk-field-message__text > * + * {
margin-block-start: var(--smbk-space-text-paragraph);
}
:is(.smbk-field-group__description, .smbk-field-group__message) .smbk-field-message__text :where(ul, ol) {
display: flex;
flex-direction: column;
gap: var(--smbk-space-text-list);
padding-inline-start: var(--smbk-space-inset-xl);
}
:is(.smbk-field-group__description, .smbk-field-group__message) .smbk-field-message__text :where(code, pre) {
font-family: var(--smbk-typography-code-sm-font-family);
font-size: var(--smbk-typography-code-sm-font-size);
font-weight: var(--smbk-typography-code-sm-font-weight);
line-height: var(--smbk-typography-code-sm-line-height);
letter-spacing: var(--smbk-typography-code-sm-letter-spacing);
}
:is(.smbk-field-group__description, .smbk-field-group__message) .smbk-field-message__text pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.stack.md--smbk-space-stack-md | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.text.label--smbk-space-text-label | 0px | Spacing | |
| space.inline.2xl--smbk-space-inline-2xl | comfortable: 24pxcompact: 16pxchanges with density | Spacing | |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| space.text.paragraph--smbk-space-text-paragraph | comfortable: 16pxcompact: 12px | Spacing | |
| space.text.list--smbk-space-text-list | comfortable: 8pxcompact: 6px | Spacing | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| typography.code-sm--smbk-typography-code-sm-font-family--smbk-typography-code-sm-font-size--smbk-typography-code-sm-font-weight--smbk-typography-code-sm-line-height--smbk-typography-code-sm-letter-spacing--smbk-typography-code-sm-text-transform | fontFamily: Geist Mono, Geist Mono Fallback, ui-monospace, monospacefontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
Manifest
{
"name": "field-group",
"block": "smbk-field-group",
"summary": "Several radios, checkboxes or choice cards under one question: a fieldset with its legend, help, and one error message for the group.",
"adapt": "css",
"axes": {
"layout": {
"values": [
"vertical",
"horizontal"
],
"default": "vertical",
"description": "Vertical stacks the options. Horizontal sets them in a row that wraps, only for a few short labels without lines under them."
}
},
"flags": {
"error": {
"description": "The answer is invalid, such as a required question left empty. Every option takes its error look and one message under the options says what to do."
},
"disabled": {
"description": "Every option is disabled; the question and its help stay readable."
}
},
"parts": [
"legend",
"description",
"options",
"option",
"message"
],
"figmaNodeId": "7101:469"
}