MultiSelect
Use CMultiSelect when people choose several fixed values and the collection should remain compact until opened. Selected values appear as noninteractive chips. A native multiple Select preserves repeated-value form submission and reset behavior.
MultiSelect at a glance
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class MultiSelectAtAGlance(Component):
template = """
<c-CField>
<c-fill name="label">Workspaces</c-fill>
<c-fill name="description">Choose every workspace that should receive this observation.</c-fill>
<c-fill name="default">
<c-CMultiSelect c-options="options" placeholder="Choose workspaces" c-value="['atlas', 'aurora']" />
</c-fill>
</c-CField>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("atlas", "Atlas research", "12 collaborators"),
CMultiSelectOption("aurora", "Aurora field notes", "7 collaborators"),
CMultiSelectOption("archive", "Archived studies", disabled=True),
]
}
preview = MultiSelectAtAGlance()
preview # noqa: B018
Submit repeated values
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class MultiSelectForm(Component):
template = """
<form x-data @submit.prevent="result = Array.from(new FormData($event.target).entries())">
<c-CField required>
<c-fill name="label">Reviewers</c-fill>
<c-fill name="default">
<c-CMultiSelect c-options="options" placeholder="Choose reviewers" name="reviewer" />
</c-fill>
</c-CField>
<c-CButton type="submit">Save</c-CButton>
<c-CButton type="reset" variant="ghost">Reset</c-CButton>
<output x-text="JSON.stringify(result)"></output>
</form>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("maya", "Maya Chen"),
CMultiSelectOption("noah", "Noah Williams"),
CMultiSelectOption("ines", "InΓͺs Silva"),
]
}
preview = MultiSelectForm()
preview # noqa: B018
Group related options
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class GroupedMultiSelect(Component):
template = """
<c-CMultiSelect
c-options="options"
placeholder="Choose a destination"
c-trigger_attrs="{'aria-label':'Destination'}"
/>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("oslo", "Oslo", group="Europe"),
CMultiSelectOption("prague", "Prague", group="Europe"),
CMultiSelectOption("kyoto", "Kyoto", group="Asia"),
CMultiSelectOption("seoul", "Seoul", group="Asia"),
]
}
preview = GroupedMultiSelect()
preview # noqa: B018
Control selection
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class ControlledMultiSelect(Component):
template = """
<div x-data>
<c-CMultiSelect
c-options="options"
placeholder="Choose channels"
c-value="['email']"
c-trigger_attrs="{'aria-label':'Notification channels'}"
$c-props="{
value:$store.multiSelectExample.value,
onValueChange:(next) => $store.multiSelectExample.value = next,
}"
/>
<p>Current: <strong x-text="$store.multiSelectExample.value.join(', ')"></strong></p>
</div>
"""
js = "Alpine.store('multiSelectExample', {value:['email']});"
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("email", "Email"),
CMultiSelectOption("push", "Push"),
CMultiSelectOption("sms", "SMS"),
]
}
preview = ControlledMultiSelect()
preview # noqa: B018
Read-only and disabled states
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class MultiSelectStates(Component):
template = """
<c-CStack>
<c-CMultiSelect
c-options="options" placeholder="Choose" c-value="['active', 'paused']" readonly
c-trigger_attrs="{'aria-label':'Read-only state'}"
/>
<c-CMultiSelect
c-options="options" placeholder="Choose" disabled
c-trigger_attrs="{'aria-label':'Disabled state'}"
/>
<c-CMultiSelect
c-options="options" placeholder="Choose" invalid
c-trigger_attrs="{'aria-label':'Invalid state'}"
/>
</c-CStack>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {"options": [CMultiSelectOption("active", "Active"), CMultiSelectOption("paused", "Paused")]}
preview = MultiSelectStates()
preview # noqa: B018
Close after each choice
By default the popup stays open so several values can be toggled efficiently. Use close_on_select for workflows that should close after every change.
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class CloseOnSelectMultiSelect(Component):
template = """
<c-CMultiSelect
c-options="options"
placeholder="Choose a delivery method"
close_on_select
c-trigger_attrs="{'aria-label':'Delivery methods'}"
/>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("courier", "Courier"),
CMultiSelectOption("pickup", "Pickup"),
CMultiSelectOption("locker", "Parcel locker"),
]
}
preview = CloseOnSelectMultiSelect()
preview # noqa: B018
Variants and sizes
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class MultiSelectVariants(Component):
template = """
<c-CStack>
<c-CMultiSelect
c-options="options" placeholder="Outline" variant="outline" size="sm"
c-trigger_attrs="{'aria-label':'Small outline'}"
/>
<c-CMultiSelect
c-options="options" placeholder="Filled" variant="filled"
c-trigger_attrs="{'aria-label':'Medium filled'}"
/>
<c-CMultiSelect
c-options="options" placeholder="Plain" variant="plain" size="lg"
c-trigger_attrs="{'aria-label':'Large plain'}"
/>
</c-CStack>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {"options": [CMultiSelectOption("one", "One"), CMultiSelectOption("two", "Two")]}
preview = MultiSelectVariants()
preview # noqa: B018
Keyboard behavior
Enter, Space, Down, or Up opens the Listbox. Down and Up move the highlight; Home and End jump to its edges; printable text performs buffered typeahead; Enter or Space toggles the highlighted value; Escape closes; and Tab closes while ordinary page navigation continues.
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class KeyboardMultiSelect(Component):
template = """
<c-CMultiSelect
c-options="options"
placeholder="Focus and use the keyboard"
loop
c-trigger_attrs="{'aria-label':'Planet keyboard example'}"
/>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"options": [
CMultiSelectOption("earth", "Earth"),
CMultiSelectOption("mars", "Mars"),
CMultiSelectOption("jupiter", "Jupiter"),
]
}
preview = KeyboardMultiSelect()
preview # noqa: B018
Customize MultiSelect
Show code
import citry_ui
from citry import Component, citry
from citry_ui import CMultiSelectOption
citry.register_library(citry_ui)
class CustomizedMultiSelect(Component):
css = """
.brand-select {
--cui-multi-select-radius: 1rem;
--cui-multi-select-selected-background: #53389e;
--cui-multi-select-selected-foreground: white;
--cui-multi-select-focus-color: #7f56d9;
inline-size: min(100%, 22rem);
}
"""
template = """
<c-CMultiSelect
class_="brand-select"
c-options="options"
placeholder="Choose a collection"
c-trigger_attrs="{'aria-label':'Collection'}"
/>
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {"options": [CMultiSelectOption("botany", "Botany"), CMultiSelectOption("astronomy", "Astronomy")]}
preview = CustomizedMultiSelect()
preview # noqa: B018
Accessibility and forms
The visible Button uses the select-only combobox pattern and keeps DOM focus while aria-activedescendant identifies the highlighted Option. A native multiple Select remains the repeated form value, validity, and reset truth. Before client initialization, that native control is the visible fallback.
Use CListbox(multiple=True) for a persistent collection, CSelect for one compact value, and CCombobox when users need text filtering or custom input.
API reference
Inputs
CMultiSelect server inputs
Server inputs are passed in a template through <c-CMultiSelect ... /> or in Python through CMultiSelect(...).
| Input | Type | Default | Effect |
|---|---|---|---|
options | Sequence[CMultiSelectOption] | required | Supplies the nonempty ordered stable collection. |
placeholder | str | required | Supplies author-localized empty-value text. |
name | str | None | None | Sets the native form field name. |
form | str | None | None | Associates the native value proxy with a Form ID. |
id | str | None | None | Sets native proxy identity and generated relationships. |
value | Sequence[str] | None | None | Sets initial selected stable values in collection order. |
open | bool | False | Sets initial popup visibility when eligible. |
required | bool | None | None | Enables native required validity outside Field. |
disabled | bool | None | None | Disables selection and form contribution. |
readonly | bool | None | None | Preserves submission while preventing changes. |
invalid | bool | None | None | Adds owner-supplied invalid presentation. |
loop | bool | False | Wraps open Listbox arrow navigation. |
close_on_select | bool | False | Closes the popup after each accepted toggle. |
placement | "bottom-start" | "bottom-end" | "top-start" | "top-end" (CMultiSelectPlacement) | "bottom-start" | Sets preferred logical popup placement. |
match_width | bool | True | Matches the popup inline size to the control within viewport limits. |
variant | "outline" | "filled" | "plain" (CMultiSelectVariant) | "outline" | Selects control treatment. |
size | "sm" | "md" | "lg" (CMultiSelectSize) | "md" | Selects control and Option geometry. |
class_ | CClassValue | None (CClassValue) | None | Adds root classes. |
style | CStyleValue | None (CStyleValue) | None | Adds root inline styles. |
attrs | Mapping[str, object] | None | None | Adds trusted nonconflicting root attributes. |
trigger_attrs | Mapping[str, object] | None | None | Adds trusted relationships events and accessible naming to the combobox Button. |
listbox_attrs | Mapping[str, object] | None | None | Adds trusted nonconflicting Listbox attributes. |
CMultiSelect client inputs
Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CMultiSelect />.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
value | string[] | null | Releases control to committed selection. | Controls the selected collection while supplied; an empty array is controlled empty. |
open | boolean | null | Releases control to committed visibility. | Controls popup visibility while supplied. |
required | bool | Uses the server or Field fallback. | Reactively changes required validity. |
disabled | bool | Uses the server or Field fallback. | Reactively disables selection. |
readonly | bool | Uses the server or Field fallback. | Reactively prevents changes while preserving submission. |
invalid | bool | Uses the server or Field fallback. | Reactively changes invalid presentation. |
loop | bool | Uses the server value. | Reactively changes arrow wrapping. |
closeOnSelect | bool | Uses the server value. | Reactively changes whether a toggle closes the popup. |
placement | CMultiSelectPlacement | Uses the server value. | Reactively changes preferred placement. |
matchWidth | bool | Uses the server value. | Reactively changes popup sizing. |
variant | CMultiSelectVariant | Uses the server value. | Reactively changes treatment. |
size | CMultiSelectSize | Uses the server value. | Reactively changes geometry. |
onValueChange | ((value: string[], detail: CMultiSelectValueChangeDetail) => void) | undefined | No component callback runs. | Receives toggle reset and structural requests. |
onOpenChange | ((open: boolean, detail: CMultiSelectOpenChangeDetail) => void) | undefined | No component callback runs. | Receives visibility requests and forced-close notices. |
Slots
-
Events
Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.
CMultiSelect events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
onValueChange | (value: string[], detail: CMultiSelectValueChangeDetail) => void (CMultiSelectValueChangeDetail) | Enabled toggle reset or structural recovery. | {value, previousValue, option, selected, controlled, source, sourceEvent} (CMultiSelectValueChangeDetail) | Commits immediately when uncontrolled and waits for owner acceptance when controlled. |
onOpenChange | (open: boolean, detail: CMultiSelectOpenChangeDetail) => void (CMultiSelectOpenChangeDetail) | Visibility request or nonrejectable safety close. | {open, reason, controlled, forced, source} (CMultiSelectOpenChangeDetail) | Controlled requests notify without changing visibility; forced safety closes always hide. |
Methods
-
CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
CMultiSelect CSS variables
Apply these variables to CMultiSelect or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
--cui-multi-select-background | color | Control and popup surface. | Canvas |
--cui-multi-select-foreground | color | Primary foreground. | CanvasText |
--cui-multi-select-placeholder-color | color | Empty-value foreground. | scheme-aware muted |
--cui-multi-select-muted-color | color | Description and disabled foreground. | scheme-aware muted |
--cui-multi-select-border-color | color | Outline border. | scheme-aware subtle border |
--cui-multi-select-hover-background | color | Highlighted Option surface. | CanvasText mix |
--cui-multi-select-selected-background | color | Selected Option surface. | scheme-aware blue |
--cui-multi-select-selected-foreground | color | Selected Option foreground. | scheme-aware blue text |
--cui-multi-select-chip-background | color | Selected-value chip surface. | CanvasText mix |
--cui-multi-select-chip-foreground | color | Selected-value chip foreground. | CanvasText |
--cui-multi-select-focus-color | color | Focus outline. | Highlight |
--cui-multi-select-radius | length | Control and popup corners. | 0.625rem |
--cui-multi-select-control-padding | length | Control padding. | size-derived |
--cui-multi-select-option-padding | length | Option padding. | size-derived |
--cui-multi-select-max-block-size | length | Popup scroll boundary. | 18rem |
--cui-multi-select-offset | length | Anchor gap. | 0.25rem |
--cui-multi-select-shadow | shadow | Popup elevation. | scheme-aware shadow |
--cui-multi-select-duration | time | Indicator rotation motion. | 120ms |
Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.
CMultiSelect attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
role | Control Button | combobox | Declares the select-only popup control. |
role | Listbox div | listbox | Declares the popup collection. |
aria-multiselectable | Listbox div | true | Declares independent multiple selection. |
role | Option div | option | Declares each value. |
aria-expanded | Control Button | true | false | Reflects popup visibility. |
aria-controls | Control Button | IDREF | Targets the Listbox. |
aria-activedescendant | Control Button | IDREF or absent | Identifies the highlighted open Option. |
aria-required | Control Button | true or absent | Mirrors effective required state. |
aria-disabled | Control Button | true or absent | Mirrors effective unavailability. |
aria-readonly | Control Button | true or absent | Mirrors read-only interaction. |
aria-invalid | Control Button | true or absent | Mirrors effective invalid presentation. |
aria-selected | Option div | true | false | Reflects effective selection. |
data-open | Root div | present-or-absent | Mirrors effective visibility. |
data-empty | Root div | present-or-absent | Mirrors no selected value. |
data-required | Root div | present-or-absent | Mirrors effective required state. |
data-readonly | Root div | present-or-absent | Mirrors read-only interaction. |
data-invalid | Root div | present-or-absent | Mirrors effective invalid presentation. |
data-close-on-select | Root div | present-or-absent | Mirrors close-after-toggle behavior. |
data-match-width | Root div | present-or-absent | Mirrors popup width matching. |
data-variant | Root div | outline | filled | plain | Mirrors effective treatment. |
data-size | Root div | sm | md | lg | Mirrors effective geometry. |
data-value | Option div | string | Exposes stable identity. |
data-selected | Option div | present-or-absent | Mirrors selection. |
data-highlighted | Option div | present-or-absent | Mirrors active descendant. |
data-disabled | Root or Option div | present-or-absent | Mirrors effective unavailability. |
data-placement | Popup div | CMultiSelectPlacement | Reflects preferred logical placement. |
Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.
CMultiSelect selectors
| Selector | Element | Purpose |
|---|---|---|
[data-citry-ui-part="root"] | Root div | Stable root attrs and state surface. |
[data-citry-ui-part="control"] | Combobox Button | Visible control and focus owner. |
[data-citry-ui-part="values"] | Values span | Selected chips or placeholder. |
[data-citry-ui-part="placeholder"] | Placeholder span | Empty-selection copy. |
[data-citry-ui-part="chip"] | Chip span | Noninteractive selected-value label. |
[data-citry-ui-part="indicator"] | Indicator span | Decorative popup-state mark. |
[data-citry-ui-part="popup"] | Manual popover div | Top-layer scrolling surface. |
[data-citry-ui-part="listbox"] | Listbox div | Semantic collection. |
[data-citry-ui-part="group"] | Group div | Related Options. |
[data-citry-ui-part="group-label"] | Group label span | Visible group name. |
[data-citry-ui-part="option"] | Option div | Value semantics and state. |
[data-citry-ui-part="option-label"] | Option label span | Accessible Option name. |
[data-citry-ui-part="option-description"] | Option description span | Supporting description. |
Interfaces
Aliases and data shapes referenced above.
Input type aliases
| Interface | Definition |
|---|---|
CClassValue | str | Mapping[str, bool] | Sequence[CClassValue] |
CStyleValue | str | Mapping[str, object] | Sequence[CStyleValue] |
CMultiSelectPlacement | Literal["bottom-start", "bottom-end", "top-start", "top-end"] |
CMultiSelectVariant | Literal["outline", "filled", "plain"] |
CMultiSelectSize | Literal["sm", "md", "lg"] |
CMultiSelectChangeSource | Literal["pointer", "keyboard", "reset", "structure"] |
CMultiSelectOpenReason | Literal["trigger", "keyboard", "selection", "escape", "tab", "outside", "focus-outside", "reset", "native", "ancestor"] |
CMultiSelectOption
| Field | Type | Default | Meaning |
|---|---|---|---|
value | str | - | Stable unique form value. |
label | str | - | Visible accessible Option name. |
description | str | None | - | Optional separately described supporting text. |
disabled | bool | - | Prevents user selection. |
group | str | None | - | Optional contiguous visible group label. |
CMultiSelectValueChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
value | string[] | - | Requested copied value collection. |
previousValue | string[] | - | Previous copied effective collection. |
option | HTMLElement | None | - | Activated Option or None for reset and structure. |
selected | bool | - | Resulting selected state for the activated Option. |
controlled | bool | - | Whether client value owns selection. |
source | CMultiSelectChangeSource | - | Request source. |
sourceEvent | Event | None | - | Native source event when present. |
CMultiSelectOpenChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
open | bool | - | Requested or forced visibility. |
reason | CMultiSelectOpenReason | - | Visibility reason. |
controlled | bool | - | Whether client open owns visibility. |
forced | bool | - | Whether safety made the close nonrejectable. |
source | EventTarget | None | - | Native source or safety owner. |
Translation keys
-