One choice drawn as a card, with a label and a line under it: picked like a radio, or ticked like a checkbox.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
marksmbk-choice-card--mark-<value> | radiocheckbox | radio | A radio when one card of a group is picked; a checkbox when several can be. |
errorsmbk-choice-card--error | On or off | Off | The choice is invalid, such as a required question left empty. The card takes the danger edge, and its mark its error look. |
Examples
Usage
Cards picked like radios: the whole card is the target, the mark and the brand edge show the choice.
Checkboxes
With a checkbox mark, several cards can be picked.
Without a line
A title alone, for short choices.
Error
The danger edge on every card, and the group’s message says what to do.
Disabled
Dimmed onto the disabled surface; a chosen card keeps its edge in the disabled color.
Guidelines
Purpose
One choice drawn as a card: a title, a line under it, and the radio's circle or the checkbox's box. The whole card is the label of one real radio or checkbox, so it is picked by a press anywhere on it. It gives a few choices that each need explaining more room and a larger target than a radio row.
When to use
- Two to five choices that each need a line of explanation: a plan, a delivery speed, a look.
- With the checkbox mark, a few add-ons that can each be picked.
When not to use
Usage rules
Do
- Keep the title to a few words and the line to one sentence.
- Pre-select the most common choice when there is one.
Don't
- Put buttons or links inside a card: a press anywhere picks it.
- Show more than about five cards in one group.
Use cards for a few choices that each need a line of explanation.
A long list of cards pushes the rest of the form out of sight; a select holds many choices in one line.
Anatomy
- The block, the card: a label element holding everything, on the default surface with a hairline edge and a lift. It also carries the radio's or the checkbox's classes, which draw the mark.
- The mark, the radio's circle or the checkbox's box, on the title's first line. It always shows, so being picked is never told by color alone.
- The text part, the title over the line.
- The label part, the title.
- The description part, an optional line under the title.
Behavior
Picking a card checks its control and draws the brand edge at the emphasis width, inside the card so it keeps its size, with the mark filled. Under the pointer the card lifts onto the raised surface; pressed, it sinks onto the sunken surface. With error, every card takes the danger edge, picked or not. Disabled, the card sits on the disabled surface with its words dimmed; a picked disabled card keeps its edge in the disabled border color.
The radio mark is the default; with the checkbox mark, several cards can be picked. Inside a field group a card takes the group's name, error and required state, and a radio card follows the chosen value when the group holds it.
Accessibility
What it does for you
- The control is a real radio or checkbox, named by the title and described by the line, so a screen reader says the title, radio or checkbox, picked or not, then the line.
- Radios in one group take one stop for the Tab key and the arrow keys move the choice; Space picks.
- The focus ring goes around the whole card.
What you must do
- Put a set of cards in a field group, whose legend names it.
How to test
- Tab to a set of radio cards: one stop takes focus, with the ring around the whole card in both themes and in a forced-color mode. Move with the arrow keys, then press Space to pick.
- With a screen reader, a card is read as its title, a radio or a checkbox, picked or not, then its line, and the set by its legend.
- With text at 200% and the page 320 pixels wide, the title and line wrap and the card stays whole.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the card. Radios in one group take one stop. |
| Up ArroworDown ArroworLeft ArroworRight Arrow | Moves the choice to the next or previous radio card in the group. |
| Space | Picks a radio card, or toggles a checkbox card. |
Source
The files the choice card is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-choice-card — Choice card. Figma "Choice card" 7105:1911 (State: Default, Hover, Pressed, Focus, Disabled, Error × Selected).
* One choice drawn as a card: the radio's circle or the checkbox's box, then a
* label in Label MD over an optional line in Body SM, the whole card the
* label of one native radio or checkbox. Built on the radio and the checkbox:
* the card carries their block and size classes, so the mark's own looks
* (hover, chosen, error, disabled) are theirs, and this stylesheet adds only
* the card: its surface, its edge, its padding and the focus ring around it.
* The label over its line, and the mark on the label's first line, are the
* rules every choice shares, in choice/choice.css.
* Chosen, the edge turns the brand color at the emphasis width, drawn inside
* so the card keeps its size, and the mark shows the choice as a shape too. */
/* Two classes, so the card wins over the radio's and the checkbox's own row whichever stylesheet loads first. */
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox) {
box-sizing: border-box;
display: flex;
align-items: flex-start;
gap: var(--smbk-space-inline-md);
width: 100%;
padding: calc(var(--smbk-space-inset-xl) - var(--smbk-border-width-hairline));
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-default);
box-shadow: var(--smbk-elevation-control);
color: var(--smbk-color-text-default);
font-family: var(--smbk-typography-label-md-font-family);
font-size: var(--smbk-typography-label-md-font-size);
font-weight: var(--smbk-typography-label-md-font-weight);
line-height: var(--smbk-typography-label-md-line-height);
letter-spacing: var(--smbk-typography-label-md-letter-spacing);
}
@media (hover: hover) {
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):hover:has(:enabled) {
background: var(--smbk-color-surface-raised);
}
}
/* Pressed: the card sinks onto the sunken surface and drops its lift. */
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):active:has(:enabled) {
background: var(--smbk-color-surface-sunken);
box-shadow: none;
}
/* Chosen: the brand edge at the emphasis width, the hairline border plus the rest drawn inside. */
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:checked) {
border-color: var(--smbk-color-border-brand);
box-shadow: inset 0 0 0 calc(var(--smbk-border-width-emphasis) - var(--smbk-border-width-hairline)) var(--smbk-color-border-brand), var(--smbk-elevation-control);
}
/* Pressed while chosen keeps the brand edge, not the lift; in error the danger edge below stays. */
.smbk-choice-card:not(.smbk-choice-card--error, :has([aria-invalid="true"])):is(.smbk-radio, .smbk-checkbox):active:has(:enabled:checked) {
box-shadow: inset 0 0 0 calc(var(--smbk-border-width-emphasis) - var(--smbk-border-width-hairline)) var(--smbk-color-border-brand);
}
/* Error, chosen or not: the danger edge at the emphasis width; its control marked aria-invalid="true" draws the same. */
.smbk-choice-card.smbk-choice-card--error:is(.smbk-radio, .smbk-checkbox):has(:enabled),
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:enabled[aria-invalid="true"]) {
border-color: var(--smbk-color-feedback-danger-border);
box-shadow: inset 0 0 0 calc(var(--smbk-border-width-emphasis) - var(--smbk-border-width-hairline)) var(--smbk-color-feedback-danger-border), var(--smbk-elevation-control);
}
/* The focus ring goes around the whole card, not the mark. */
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:focus-visible) {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
.smbk-choice-card :is(.smbk-radio__control, .smbk-checkbox__control):focus-visible + :is(.smbk-radio__circle, .smbk-checkbox__box) {
outline: none;
}
.smbk-choice-card .smbk-radio__control:not(:checked):focus-visible + .smbk-radio__circle {
background: transparent;
}
/* Disabled: the disabled surface with no lift; chosen, the edge keeps its width in the disabled border color. */
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:disabled) {
background: var(--smbk-color-surface-disabled);
box-shadow: none;
}
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:disabled:checked) {
border-color: var(--smbk-color-action-disabled-border);
box-shadow: inset 0 0 0 calc(var(--smbk-border-width-emphasis) - var(--smbk-border-width-hairline)) var(--smbk-color-action-disabled-border);
}
/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops shadows, so the chosen card's edge is the border alone:
Highlight, as the chosen mark is. Nothing here applies outside that mode. */
@media (forced-colors: active) {
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:checked) {
border-color: Highlight;
}
.smbk-choice-card:is(.smbk-radio, .smbk-checkbox):has(:disabled) {
border-color: GrayText;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.text.label--smbk-space-text-label | 0px | Spacing | |
| size.selection.md--smbk-size-selection-md | 20px | Size | |
| color.text.subtlest--smbk-color-text-subtlest | light: #606a75dark: #7c8692changes with theme | Color | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| radius.container.md--smbk-radius-container-md | 12px | Radius | |
| color.surface.default--smbk-color-surface-default | light: #f3f5f8dark: #181a1cchanges with theme | Color | |
| elevation.control--smbk-elevation-control | light: 0px 0px 1px 0px #373d430d, 0px 1px 2px 0px #373d431a, 0px 2px 4px 0px #373d430ddark: 0px 0px 1px 0px #0000001a, 0px 1px 2px 0px #00000026, 0px 2px 4px 0px #0000001a | Elevation | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| typography.label-md--smbk-typography-label-md-font-family--smbk-typography-label-md-font-size--smbk-typography-label-md-font-weight--smbk-typography-label-md-line-height--smbk-typography-label-md-letter-spacing--smbk-typography-label-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.raised--smbk-color-surface-raised | light: #f9fafbdark: #232527changes with theme | Color | |
| color.surface.sunken--smbk-color-surface-sunken | light: #373d430ddark: #00000066changes with theme | Color | |
| color.border.brand--smbk-color-border-brand | #cc0d0d | Color | |
| border-width.emphasis--smbk-border-width-emphasis | 2px | Border width | |
| color.feedback.danger.border--smbk-color-feedback-danger-border | #f34336 | Color | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| color.surface.disabled--smbk-color-surface-disabled | light: #e7ebf0dark: #373d43changes with theme | Color | |
| color.action.disabled.border--smbk-color-action-disabled-border | light: #c4cdd8dark: #2d3237changes with theme | Color |
Manifest
{
"name": "choice-card",
"block": "smbk-choice-card",
"summary": "One choice drawn as a card, with a label and a line under it: picked like a radio, or ticked like a checkbox.",
"adapt": "css",
"axes": {
"mark": {
"values": [
"radio",
"checkbox"
],
"default": "radio",
"description": "A radio when one card of a group is picked; a checkbox when several can be."
}
},
"flags": {
"error": {
"description": "The choice is invalid, such as a required question left empty. The card takes the danger edge, and its mark its error look."
}
},
"parts": [
"text",
"label",
"description"
],
"figmaNodeId": "7105:1911"
}