Card-style choices for options that need a sentence of explanation, an icon or a price. Single mode has radio semantics and multiple has checkbox semantics. variant="tile" lays large tiles out in a columns grid for plan pickers; list stacks rows. Form-associated: single posts name=value, multiple posts one entry per picked value.
import { Component } from "@angular/core";
import { SuiChoiceCards } from "@shineui/angular";
@Component({
standalone: true,
imports: [SuiChoiceCards],
template: `<sui-choice-cards label="Payment method" name="method" value="ach" [options]="options" (suiChange)="onChange($event)"></sui-choice-cards>`,
})
export class Example {
options = [
{ label: "ACH", value: "ach", description: "Free. Lands in 2 to 3 days.", icon: "fa-solid fa-building-columns" },
{ label: "Card", value: "card", description: "2.9% fee. Posts the same day.", icon: "fa-regular fa-credit-card" },
{ label: "Check", value: "check", description: "Recorded when the deposit clears.", icon: "fa-regular fa-money-bill-1", meta: "Manual" },
]; // SuiChoiceCardOption[]
onChange(e: CustomEvent) { console.log(e.detail); }
}
Basic
Single mode is a role="radiogroup" of role="radio" cards with aria-checked, one of which is the tab stop. A card can carry an icon, a description and a short meta note.
multiple gives checkbox semantics: a role="group" of role="checkbox" cards, every one a tab stop. A picked card draws a check in its mark.
<div style="max-width:480px">
<sui-choice-cards id="cc-multi" label="Add-ons" name="addon" multiple help="Add-ons bill monthly with the plan."></sui-choice-cards>
</div>
// setup(root): properties and events
setup(root) {
const el = root.querySelector('#cc-multi');
el.options = addOns;
el.value = ['payroll'];
}
States
The label and the message line sit outside the group, so a screen reader does not read the error again while the user arrows through the cards. A disabled option cannot be picked and posts nothing.
<div style="display:grid;gap:18px;max-width:480px">
<sui-choice-cards id="cc-req" label="Payment method" name="cc-method-req" required help="Pick how this customer pays."></sui-choice-cards>
<sui-choice-cards id="cc-err" label="Payment method" name="cc-method-err" required error="Payment method is required."></sui-choice-cards>
<sui-choice-cards id="cc-off" label="Payment method" name="cc-method-off" value="ach" disabled help="Locked while a payment is in flight."></sui-choice-cards>
</div>
readonly keeps the cards named and in the tab order and refuses a pick while the set still posts. invalid paints every card in the danger tone without a message. The tile grid holds at most columns tiles and never one under 160px, so a phone shows one column.
Single mode posts one entry under the host name and multiple posts one entry per picked value. An unpicked single set posts an empty string rather than dropping the key.
<form id="cc-form" style="display:grid;gap:18px;max-width:480px">
<sui-choice-cards id="cc-form-method" label="Payment method" name="method" required></sui-choice-cards>
<sui-choice-cards id="cc-form-addons" label="Add-ons" name="addon" multiple></sui-choice-cards>
<sui-button type="submit" variant="brand" label="Save billing"></sui-button>
<output id="cc-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output>
</form>
Single mode has one tab stop: the picked card, or the first enabled one. With multiple, every card is a tab stop.
ArrowDownArrowRight
Focuses the next enabled card, wrapping to the first; in single mode also picks it.
ArrowUpArrowLeft
Focuses the previous enabled card, wrapping to the last; in single mode also picks it.
SpaceEnter
Picks the focused card; with multiple, toggles it.
API
<sui-choice-cards>
Properties
Property
Attribute
Type
Default
Description
columns
columns
number
3
Grid columns for the tile variant: the most the row holds. A tile never goes under 160px, so a narrow screen shows fewer columns and a phone one.
disabled
disabled
boolean
false
Disables every card. A disabled set posts nothing and never holds the form.
error
error
stringstring | undefined
Error message; paints every card in the danger tone and sets aria-invalid.
help
help
stringstring | undefined
Help text under the cards. Replaced by error when set.
invalid
invalid
boolean
false
Danger border on every card without a message, for a form that draws its summary elsewhere.
label
label
stringstring | undefined
Group label (12.5px / 620).
multiple
multiple
boolean
false
Checkbox semantics: a role="group" of role="checkbox" cards, any number of which can be picked. A picked card draws a check in its mark. Single mode is a role="radiogroup" of role="radio" cards.
name
name
stringstring | undefined
Form control name. Reflected: the form entry is built from the content attribute.
options
property only
SuiChoiceCardOption[]
[]
Options { label, value, description, icon, meta, disabled }[]. meta is a short trailing note such as a price. A disabled option cannot be picked and posts nothing. Property, not attribute.
readOnly
readonly
boolean
false
Display only: every card keeps its name and its tab stop but a click, Space, Enter or an arrow key changes nothing, and the set still posts. Neither a read-only nor a disabled set holds the form on valueMissing.
required
required
boolean
false
A selection is required for the surrounding form to be valid.
value
value
SuiChoiceValueSuiKey[] | number | string | undefined
The picked card's value, or the array of picked values when multiple, each as its option gives it (a number stays a number). Written as an attribute it is text, and it still picks the card whose value reads the same. Mutable; written before suiChange fires.
variant
variant
"list" | "tile"
'list'
list stacks rows (default); tile renders large tiles in a grid with meta in the tile header.
Events
Event
Detail
React
Vue
Description
suiChange
SuiChoiceValueSuiKey[] | number | string
onSuiChange
@sui-change
Fires when the selection changes; detail is the new value: the picked card's value, or the array of picked values when multiple.
Methods
Method
Signature
Description
setFocus()
setFocus() => Promise<void>
Moves keyboard focus to the card that holds the tab stop, as el.focus() does. sui-form calls this to reach the first invalid control.
Types
SuiChoiceCardOptioninterface
label
string
value
SuiKey
What value holds, as given: a number stays a number through value, suiChange and a framework binding. The form posts its text.
description?
string
icon?
string
meta?
string
disabled?
boolean
SuiChoiceValuetypeWhat a choice element holds: one option's value, or, where it takes several (multiple), the array of the picked values. A value keeps the type its option gave it, so a numeric id comes back a number through value, the events and a framework binding; only the form posts text.
No members.
SuiKeytypeA record's or an item's key wherever the library identifies one: a row, a tree node, a list item, a selected value. Numbers stay numbers, so a numeric id round-trips through selection, the events and a framework binding without turning into a string.
No members.
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.