Skip to content

StatusMultiPicker

Picks any number of status codes for an entity type, offering only the codes the project it is scoped to allows.

Single and multi: StatusPicker takes one code.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/status-multi-picker.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/status-multi-picker.json
StatusMultiPicker: per-project options, a mandatory field, trigger summaries, unknown codes, states and sizes

React

Loading the React demo…

Svelte

Loading the Svelte demo…

proptypedefaultmeaning
searchPlaceholderstring'Search statuses…'Placeholder of the search box.
summary'chips' | 'ellipsis' | 'count''ellipsis'How many of the selection the control shows.
badge'both' | 'icon' | 'text' | 'glyph''both'What one selected status is drawn as.
maxnumber0Badges drawn before the rest becomes +n. 0 lets the row fit what it can, and badge="icon" draws twice as many.
contextfrom StatusPickerSgContextrequiredThe widget context. The options and the status table are read through it, once per page.
entityTypefrom StatusPickerstringrequiredThe type whose status field is offered.
projectIdfrom StatusPickernumber—Offer the codes this project allows.
projectIdsfrom StatusPickernumber[]—Offer the codes every one of these projects allows.
fieldfrom StatusPickerstringstatus fieldA list or status field other than the type's own.
valuestring[][]The selected codes. bind:value in Svelte, value with onValueChange in React.
placeholderstring'Select statuses'Shown with nothing selected.
emptyLabelstring'No match'Shown when the search matches nothing.
loadingLabelfrom StatusPickerstring'Loading…'Names the skeletons a read stands behind, for a screen reader.
errorLabelfrom StatusPickerstring—Shown in place of what the failed read said.
clearablefrom StatusPickerbooleanthe fieldOffers a control that clears the value. Unset, it follows the field: a mandatory one is never clearable.
readonlyfrom StatusPickerbooleanfalseKeeps full contrast and drops the chevron, the popup and the clear control.
disabledfrom StatusPickerbooleanfalseGreys the control and takes it out of the tab order.
invalidfrom StatusPickerbooleanfalseSets aria-invalid and the destructive ring.
showCodefrom StatusPickerbooleantrueDraws the code as a row's right-aligned secondary, when it says more than the label.
secondaryfrom StatusPicker(option) => stringthe codeA row's right-aligned value, of the caller's own making.
subLabelfrom StatusPicker(option) => string—The muted line under a row's label.
siteUrlfrom StatusPickerstringthe context'sPassed to every badge, for a sprite cell outside the status icon set.
sizefrom StatusPicker'sm' | 'md' | 'lg''md'Control height: 7, 8 and 9.
openfrom StatusPickerbooleanfalseWhether the popup is showing. Two-way in Svelte.
class / classNamefrom StatusPickerstring—Merged after the widget's own classes.

Every other attribute is spread onto the root: id, aria-*, data-*, key handlers and a ref to the root element.

summary and badge are two axes. summary says how many of the selection the control shows: chips makes it a token field with every badge, wrapped, each with its own remove control inside the badge, the caret beside them, and +n after the last badge max allows; ellipsis makes it a one-line trigger with as many whole badges as the row fits, never a cut one, then +n inline after the last, and the search box at the top of the popup; count is the same trigger reading 3 selected.

badge says what one status is drawn as: both is the icon and the label, icon drops the label and the per-badge remove, so removal happens in the list and twice as many fit, and text is the label alone. A row is the row anatomy of the design rules, after its checkbox: the status icon as the leading mark, the display name with the matched runs bold, and the code right-aligned. It is what the filter bar draws for a status facet, so a page listing statuses twice lists them the same way. A status has one display name and one icon, so the row’s labelField and thumbnail are fixed, and it has no fields of its own, so its secondary is a function rather than a field name. The control keeps the badges, where a status is a value rather than an option.

A press on +n opens the list, where the hidden codes are. Without a project the options are the site vocabulary, which hides nothing. projectIds offers the intersection of what each project allows, in the first project’s order.

The root carries the size and, while the options load, data-loading.

eventpayloadwhen
onValueChangestring[]A status was ticked or unticked.
onOpenChangefrom StatusPickerbooleanThe popup opened or closed.

onValueChange fires with the whole array on every change, including the empty array from the clear control. A selected code the option set does not carry is kept, never dropped.

onOpenChange fires when the popup opens or closes. Svelte also binds it with bind:open.

None. The rows are the shared picker row; the closed state is status badges.

keydoes
Space, EnterOn the chevron, opens the list
any textNarrows the rows, from the caret under chips and from the popup's search box otherwise
Home EndFirst and last row
Space, Enter, ↓from StatusPickerOpens the list
↑ ↓Moves the highlight and keeps it in view; the last row holds it rather than wrapping
EnterToggles the highlighted status; the list stays open
BackspaceIn an empty search box, highlights the last chip; a second one removes it
EscCloses, keeps the selection and clears the query; on a closed control it does nothing
Tabfrom StatusPickerLeaves the control, or reaches the clear control when there is one

A project’s usable statuses are valid_values minus hidden_values, read with project_id. valid_values is the site’s whole vocabulary and is byte-identical at every scope, so reading it alone tells you nothing about a project (probe 009).

REST does not enforce hidden_values on write: a hidden status writes and reads back. Subtracting is right for offering a choice and wrong for testing a row, so a selected code outside the option set keeps a row of its own here, labelled with the code (field_types/status_list).

A status list has no substring operator, so there is no server-side type-ahead over it. The vocabulary is read once and the query input narrows it in the browser (field_types/status_list).

Labels come from display_values, and a missing key falls back to the raw code rather than dropping the option (probe 009).

Project’s status field is sg_status, a plain list rather than a status_list. There is no Status row behind a list, so those options carry no icon and no colour (entity_types/Project).

The row, chip and checkbox anatomy follows shadcn’s Base UI Combobox, read through shadcn 4.21.0. The primitive underneath is Base UI Combobox 1.8.0 in React and Bits UI Combobox 2.19.1 in Svelte. shadcn-svelte ships no combobox item, so each widget composes the headless primitive of its framework and both draw the same rows, the same classes and the same states.