Theme
Version
GitHub PyPI Discord
On this page

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

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

Submit a MultiSelect
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 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

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

MultiSelect 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.

Close after selection
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

MultiSelect 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.

Navigate MultiSelect
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

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(...).

InputTypeDefaultEffect
optionsSequence[CMultiSelectOption]requiredSupplies the nonempty ordered stable collection.
placeholderstrrequiredSupplies author-localized empty-value text.
namestr | NoneNoneSets the native form field name.
formstr | NoneNoneAssociates the native value proxy with a Form ID.
idstr | NoneNoneSets native proxy identity and generated relationships.
valueSequence[str] | NoneNoneSets initial selected stable values in collection order.
openboolFalseSets initial popup visibility when eligible.
requiredbool | NoneNoneEnables native required validity outside Field.
disabledbool | NoneNoneDisables selection and form contribution.
readonlybool | NoneNonePreserves submission while preventing changes.
invalidbool | NoneNoneAdds owner-supplied invalid presentation.
loopboolFalseWraps open Listbox arrow navigation.
close_on_selectboolFalseCloses the popup after each accepted toggle.
placement"bottom-start" | "bottom-end" | "top-start" | "top-end" (CMultiSelectPlacement)"bottom-start"Sets preferred logical popup placement.
match_widthboolTrueMatches 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)NoneAdds root classes.
styleCStyleValue | None (CStyleValue)NoneAdds root inline styles.
attrsMapping[str, object] | NoneNoneAdds trusted nonconflicting root attributes.
trigger_attrsMapping[str, object] | NoneNoneAdds trusted relationships events and accessible naming to the combobox Button.
listbox_attrsMapping[str, object] | NoneNoneAdds trusted nonconflicting Listbox attributes.

CMultiSelect client inputs

Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CMultiSelect />.

InputTypeOmitted behaviorEffect
valuestring[] | nullReleases control to committed selection.Controls the selected collection while supplied; an empty array is controlled empty.
openboolean | nullReleases control to committed visibility.Controls popup visibility while supplied.
requiredboolUses the server or Field fallback.Reactively changes required validity.
disabledboolUses the server or Field fallback.Reactively disables selection.
readonlyboolUses the server or Field fallback.Reactively prevents changes while preserving submission.
invalidboolUses the server or Field fallback.Reactively changes invalid presentation.
loopboolUses the server value.Reactively changes arrow wrapping.
closeOnSelectboolUses the server value.Reactively changes whether a toggle closes the popup.
placementCMultiSelectPlacementUses the server value.Reactively changes preferred placement.
matchWidthboolUses the server value.Reactively changes popup sizing.
variantCMultiSelectVariantUses the server value.Reactively changes treatment.
sizeCMultiSelectSizeUses the server value.Reactively changes geometry.
onValueChange((value: string[], detail: CMultiSelectValueChangeDetail) => void) | undefinedNo component callback runs.Receives toggle reset and structural requests.
onOpenChange((open: boolean, detail: CMultiSelectOpenChangeDetail) => void) | undefinedNo 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

EventSignatureTrigger and timingDetailControlled 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.

VariableTypePurposeDefault
--cui-multi-select-backgroundcolorControl and popup surface.Canvas
--cui-multi-select-foregroundcolorPrimary foreground.CanvasText
--cui-multi-select-placeholder-colorcolorEmpty-value foreground.scheme-aware muted
--cui-multi-select-muted-colorcolorDescription and disabled foreground.scheme-aware muted
--cui-multi-select-border-colorcolorOutline border.scheme-aware subtle border
--cui-multi-select-hover-backgroundcolorHighlighted Option surface.CanvasText mix
--cui-multi-select-selected-backgroundcolorSelected Option surface.scheme-aware blue
--cui-multi-select-selected-foregroundcolorSelected Option foreground.scheme-aware blue text
--cui-multi-select-chip-backgroundcolorSelected-value chip surface.CanvasText mix
--cui-multi-select-chip-foregroundcolorSelected-value chip foreground.CanvasText
--cui-multi-select-focus-colorcolorFocus outline.Highlight
--cui-multi-select-radiuslengthControl and popup corners.0.625rem
--cui-multi-select-control-paddinglengthControl padding.size-derived
--cui-multi-select-option-paddinglengthOption padding.size-derived
--cui-multi-select-max-block-sizelengthPopup scroll boundary.18rem
--cui-multi-select-offsetlengthAnchor gap.0.25rem
--cui-multi-select-shadowshadowPopup elevation.scheme-aware shadow
--cui-multi-select-durationtimeIndicator rotation motion.120ms

Attributes

HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.

CMultiSelect attributes

AttributeElementTypeMeaning
roleControl ButtoncomboboxDeclares the select-only popup control.
roleListbox divlistboxDeclares the popup collection.
aria-multiselectableListbox divtrueDeclares independent multiple selection.
roleOption divoptionDeclares each value.
aria-expandedControl Buttontrue | falseReflects popup visibility.
aria-controlsControl ButtonIDREFTargets the Listbox.
aria-activedescendantControl ButtonIDREF or absentIdentifies the highlighted open Option.
aria-requiredControl Buttontrue or absentMirrors effective required state.
aria-disabledControl Buttontrue or absentMirrors effective unavailability.
aria-readonlyControl Buttontrue or absentMirrors read-only interaction.
aria-invalidControl Buttontrue or absentMirrors effective invalid presentation.
aria-selectedOption divtrue | falseReflects effective selection.
data-openRoot divpresent-or-absentMirrors effective visibility.
data-emptyRoot divpresent-or-absentMirrors no selected value.
data-requiredRoot divpresent-or-absentMirrors effective required state.
data-readonlyRoot divpresent-or-absentMirrors read-only interaction.
data-invalidRoot divpresent-or-absentMirrors effective invalid presentation.
data-close-on-selectRoot divpresent-or-absentMirrors close-after-toggle behavior.
data-match-widthRoot divpresent-or-absentMirrors popup width matching.
data-variantRoot divoutline | filled | plainMirrors effective treatment.
data-sizeRoot divsm | md | lgMirrors effective geometry.
data-valueOption divstringExposes stable identity.
data-selectedOption divpresent-or-absentMirrors selection.
data-highlightedOption divpresent-or-absentMirrors active descendant.
data-disabledRoot or Option divpresent-or-absentMirrors effective unavailability.
data-placementPopup divCMultiSelectPlacementReflects preferred logical placement.

Selectors

Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.

CMultiSelect selectors

SelectorElementPurpose
[data-citry-ui-part="root"]Root divStable root attrs and state surface.
[data-citry-ui-part="control"]Combobox ButtonVisible control and focus owner.
[data-citry-ui-part="values"]Values spanSelected chips or placeholder.
[data-citry-ui-part="placeholder"]Placeholder spanEmpty-selection copy.
[data-citry-ui-part="chip"]Chip spanNoninteractive selected-value label.
[data-citry-ui-part="indicator"]Indicator spanDecorative popup-state mark.
[data-citry-ui-part="popup"]Manual popover divTop-layer scrolling surface.
[data-citry-ui-part="listbox"]Listbox divSemantic collection.
[data-citry-ui-part="group"]Group divRelated Options.
[data-citry-ui-part="group-label"]Group label spanVisible group name.
[data-citry-ui-part="option"]Option divValue semantics and state.
[data-citry-ui-part="option-label"]Option label spanAccessible Option name.
[data-citry-ui-part="option-description"]Option description spanSupporting description.

Interfaces

Aliases and data shapes referenced above.

Input type aliases

InterfaceDefinition
CClassValuestr | Mapping[str, bool] | Sequence[CClassValue]
CStyleValuestr | Mapping[str, object] | Sequence[CStyleValue]
CMultiSelectPlacementLiteral["bottom-start", "bottom-end", "top-start", "top-end"]
CMultiSelectVariantLiteral["outline", "filled", "plain"]
CMultiSelectSizeLiteral["sm", "md", "lg"]
CMultiSelectChangeSourceLiteral["pointer", "keyboard", "reset", "structure"]
CMultiSelectOpenReasonLiteral["trigger", "keyboard", "selection", "escape", "tab", "outside", "focus-outside", "reset", "native", "ancestor"]

CMultiSelectOption

FieldTypeDefaultMeaning
valuestr-Stable unique form value.
labelstr-Visible accessible Option name.
descriptionstr | None-Optional separately described supporting text.
disabledbool-Prevents user selection.
groupstr | None-Optional contiguous visible group label.

CMultiSelectValueChangeDetail

FieldTypeDefaultMeaning
valuestring[]-Requested copied value collection.
previousValuestring[]-Previous copied effective collection.
optionHTMLElement | None-Activated Option or None for reset and structure.
selectedbool-Resulting selected state for the activated Option.
controlledbool-Whether client value owns selection.
sourceCMultiSelectChangeSource-Request source.
sourceEventEvent | None-Native source event when present.

CMultiSelectOpenChangeDetail

FieldTypeDefaultMeaning
openbool-Requested or forced visibility.
reasonCMultiSelectOpenReason-Visibility reason.
controlledbool-Whether client open owns visibility.
forcedbool-Whether safety made the close nonrejectable.
sourceEventTarget | None-Native source or safety owner.

Translation keys

-