# MultiSelector

- Page: https://jiguang132.com/resources/astryx/docs/multi-selector
- Resource: Astryx (https://jiguang132.com/resources/astryx.md)
- All guides: https://jiguang132.com/resources/astryx/docs.md

A checkbox dropdown for selecting multiple values from a list. Selected items can display as a count, labels, or badges. Use it for filtering or when presenting a finite set of options where multiple choices are needed.

## Installation

```bash
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
```

Requires React 19+, base CSS and a Theme provider. See [Setup](/resources/astryx/docs/getting-started).

## Multi Selector

A checkbox dropdown for selecting multiple values from a list. Selected items can display as a count, labels, or badges. Use it for filtering or when presenting a finite set of options where multiple choices are needed.

### Usage guidance

- Use for a moderate, finite set of options where multiple choices are needed.
- Enable search filtering when the list exceeds ~15 options.
- Use renderOption for custom option rows; the checkbox affordance remains owned by MultiSelector.
- Enable select-all when most users will want all or nearly all options selected.
- Use inside InputGroup only when the control needs a short prefix or suffix addon as part of one decorated input surface; prefer count or labels trigger display so the group stays single-line.
- Use variant="secondary" when a multi-selector sits beside secondary buttons or segmented controls, and variant="ghost" when it sits beside ghost buttons. If validation status is needed there, prefer statusVariant="tooltip" so the toolbar height stays compact.
- Use presentation="adaptive" when the multi-selector should become a bottom sheet on compact touch screens.
- Avoid: Use for single-value selection; use Selector instead.
- Avoid: Show more than ~20 options without enabling search.
- Avoid: Wrap a disabled MultiSelector in Tooltip to explain why it is disabled; disabled triggers swallow the hover events the wrapper needs. Use the disabledMessage prop instead.

### Theming API

```json
{
  "targets": [
    {
      "className": "astryx-multi-selector",
      "visualProps": [
        "variant",
        "size",
        "status"
      ],
      "states": [
        "disabled",
        "readonly"
      ]
    },
    {
      "className": "astryx-multi-selector-empty-state"
    },
    {
      "className": "astryx-multi-selector-search"
    },
    {
      "className": "astryx-multi-selector-section-heading"
    },
    {
      "className": "astryx-multi-selector-indicator-icon",
      "states": [
        "state"
      ]
    },
    {
      "className": "astryx-multi-selector-option",
      "visualProps": [
        "size"
      ],
      "states": [
        "select-all",
        "selected",
        "disabled"
      ]
    },
    {
      "className": "astryx-multi-selector-popup"
    }
  ]
}
```

## Multi Selector

Multi-select dropdown with checkboxes for choosing multiple items.

| Prop | Type | Default | Description |
|---|---|---|---|
| label * | string |  | Label text for accessibility. |
| options * | MultiSelectorOptionType[] |  | Array of items: strings, objects with value/label/icon/disabled, dividers, or sections. An option may carry `action`: one node — an IconButton, a Button, a menu trigger — the caller renders and names. Once any option does, the popup is a grid whose rows pair the option with its action: Up/Down move rows, the inline-end arrow reaches the action, Enter fires it, pointer and touch press it directly, and pressing it never changes the selection. |
| value * | string[] |  | Currently selected values. |
| onChange * | (value: string[], change?: MultiSelectorChange) => void |  | Called when the selection changes. A creation (hasCreate) arrives with `{type: "create", query}` as the second argument and the query appended to value; the caller adds the matching option in that same update. Every other change passes no descriptor, so a one-argument handler keeps working. |
| changeAction | (value: string[]) => void \| Promise<void> |  | Async action on change. Fires after onChange. |
| placeholder | string | 'Select...' | Placeholder text shown when no value is selected. |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Size variant for the selector. |
| variant | 'input' \| 'secondary' \| 'ghost' | 'input' | Visual trigger style. input is the bordered input treatment for forms; secondary matches the secondary buttons of the active theme (borderless and neutral-filled by default) for navs and toolbars; ghost is borderless and transparent, matching ghost buttons for toolbar usage. |
| triggerDisplay | 'count' \| 'labels' \| 'badges' | 'count' | How to display selected items in the trigger. |
| formatValue | (items: {value: string; label: string}[]) => string |  | Formats the trigger text when triggerDisplay="count" or "labels". Receives the selected items (value plus resolved label); the count is items.length. Not used by triggerDisplay="badges". |
| maxBadges | number | 3 | Maximum badges to show before "+N". Only for triggerDisplay="badges". |
| hasSelectAll | boolean |  | Whether to show a select-all checkbox. |
| selectAllLabel | string | 'Select all' | Label for the select-all checkbox. |
| hasSearch | boolean |  | Whether to show a search input for filtering options. As the user types, the match count (or "No results found") is announced to screen readers via a polite live region. The search field has built-in affordances: a leading magnifier icon and, once a query is typed, a trailing clear (✕) button that resets the query and returns focus to the input. |
| searchPlaceholder | string | 'Search...' | Placeholder text for the search input. |
| emptyText | ReactNode | 'No options' | Content shown in the dropdown panel when there are no options to show, and announced in a polite live region when the panel opens. The announcement is the text this content renders, read from the DOM, so an element is announced as written and aria-hidden parts are left out of both. Not shown while isLoading. |
| emptySearchText | ReactNode | 'No results found' | Content shown in the dropdown panel when a search query matches no options, and announced in a polite live region at the same time. The announcement is the text this content renders, read from the DOM, so an element is announced as written and aria-hidden parts are left out of both. |
| hasCreate | boolean | false | With hasSearch, offers a `Create "<query>"` row first in the list when the typed text equals no option label under the search’s case-insensitive matching. Picking it, or Enter with nothing highlighted, calls onChange with the query appended to value and a `{type: "create", query}` descriptor, then clears the search; the caller must add an option for the new value in that update. Nothing is offered while isLoading. Without hasSearch it warns in development and offers nothing. |
| isDisabled | boolean |  | Disables the selector. |
| isReadOnly | boolean | false | Makes the selector read-only: the selected values stay visible, focusable, and included in form submission, and retain their combobox identity with aria-readonly. The selection surface, clear action, and disclosure indicator are removed. Unlike isDisabled, the control is not dimmed. isDisabled takes precedence when both are set. |
| htmlName | string |  | The HTML name attribute for form submissions. Renders one hidden input per selected value, like a native multi-select. |
| disabledMessage | string |  | Explains why the selector is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled MultiSelector in Tooltip. Disabled controls swallow the hover events an external Tooltip needs. |
| isLabelHidden | boolean |  | Visually hides the label while keeping it accessible. |
| description | string |  | Helper text displayed below the label. |
| isOptional | boolean |  | Marks the field as optional. |
| isRequired | boolean |  | Marks the field as required. |
| isLoading | boolean |  | Shows a loading spinner in the trigger. |
| status | {type: 'error' \| 'warning' \| 'success', message?: string} |  | Validation status with an optional message. |
| statusVariant | 'attached' \| 'detached' \| 'tooltip' | 'attached' for input selectors; 'detached' for secondary and ghost selectors | How the status message is placed relative to the input. attached overlaps directly below the bordered input and is only valid for the input variant; secondary and ghost selectors detach attached status messages by default. Use tooltip for compact toolbar controls. |
| renderOption | (option: MultiSelectorOptionData) => ReactNode |  | Custom render function for each selectable option in the dropdown. Not called for dividers, sections, or the select-all row. |
| indicatorPosition | 'start' \| 'end' | 'start' | Which edge of the option row carries the checkbox. end pushes it to the far edge of the row, including on the select-all row. |
| presentation | 'popover' \| 'bottom-sheet' \| 'adaptive' | 'popover' | How the option list is presented. adaptive uses a bottom sheet on compact touch screens and an anchored popover otherwise. |
| width | SizeValue |  | Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned. |
| startIcon | IconType \| ReactNode |  | Icon displayed at the start of the selector trigger. |
| hasClear | boolean | false | Shows a clear button when values are selected. |
| isDefaultOpen | boolean | false | Whether the dropdown starts open on mount. |
| renderTrigger | (props: MultiSelectorRenderTriggerProps) => ReactNode |  | Render the control the panel hangs off — a glyph in a list row, a chip, an icon button — instead of the selector's own field and button. Spread the given props ({ref, id, onClick, onKeyDown, onFocus, aria-haspopup, aria-expanded, aria-controls, aria-busy}) onto it; the listbox is anchored to that control and named by `label`. The field chrome (Field, status, clear, spinner) is not rendered. Pair with handleRef to open from a keystroke elsewhere. |
| handleRef | React.Ref<MultiSelectorHandle> |  | Imperative handle with open(), close(), toggle() and isOpen(). Prefer it over mirroring open state in the parent; the selector owns its visibility. |
| onOpenChange | (isOpen: boolean) => void |  | Called whenever the panel opens or closes, however it happened: the trigger, the keyboard, a light dismiss, Escape, or the handle. |
| onClick | (event: MouseEvent) => void |  | Click handler on the trigger (or the renderTrigger control). Runs after the built-in toggle and cannot cancel it. |
| onFocus | (event: FocusEvent) => void |  | Focus handler on the trigger. Runs before the selector's own focus handling. |
| onKeyDown | (event: KeyboardEvent) => void |  | Key handler on the trigger button. Runs after the built-in keyboard model and is skipped for keys it handles and cancels. |
| containerXstyle | StyleXStyles |  | StyleX styles for the field container (label, control, and status together). No effect inside InputGroup (style the InputGroup instead) or in a `horizontal-labels` FormLayout. Root `xstyle`, `className`, and `style` are not accepted. |
| containerClassName | string |  | Class name for the field container. Same targeting as `containerXstyle`. |
| containerStyle | CSSProperties |  | Inline styles for the field container. Same targeting as `containerXstyle`. |
| controlXstyle | StyleXStyles |  | StyleX styles for the visual control box: the element that paints the border and background and holds the value, icons, and end controls. Never the trigger button itself. |
| controlClassName | string |  | Class name for the visual control box. |
| controlStyle | CSSProperties |  | Inline styles for the visual control box. |

### Anatomy

- **Field**: Standalone Field shell that provides the label and optional supporting content; omitted inside InputGroup.
- **Trigger**: Painted control that displays the current selection or placeholder and opens the selection surface when editable.
- **Icon-rendered start icon**: Optional leading semantic icon or icon component rendered through Icon.
- **Caller-rendered start content**: Optional arbitrary React content rendered directly at the start of the trigger.
- **Trigger clear button**: Shared clear action that removes every selected value when hasClear is enabled.
- **Status icon**: Status glyph shown in place of the disclosure indicator for attached or tooltip status.
- **Indicator icon**: Trailing chevron shown when status presentation does not replace it; reflects collapsed or expanded state.
- **Search row**: Panel header with a borderless search input and optional clear action.
- **Search icon**: Leading magnifier rendered through Icon inside the search row.
- **Search clear button**: Shared clear action shown in the search row while a query is present.
- **Option row**: Selectable row for an option or the optional select-all choice.
- **Option checkbox indicator**: CheckboxInput indicator that presents each row’s selected, unselected, or indeterminate state.
- **Option divider**: Divider supplied in the public options data to separate adjacent option groups.
- **Section heading**: Visible heading for a labeled group of option rows.
- **Empty state**: Message shown when the shared panel content has no options or no search matches.
- **Pointer popup**: Anchored painted surface that hosts the shared panel content for popover presentation.
- **Touch sheet heading**: Heading above the shared panel content in bottom-sheet presentation.
- **Touch sheet**: BottomSheet surface that hosts the same panel content for bottom-sheet presentation.


## Examples

- Multi Selector: 
- MultiSelector — Bottom Sheet: Keeps a multi-selection list open in a bottom sheet while choices are toggled.
- MultiSelector — Column Visibility: Column visibility toggle with hidden label, search, select-all, and selection count.
- MultiSelector — Form Composition: Two multi-selectors in a form with required/optional states.
- MultiSelector — Ghost Toolbar: Borderless MultiSelector variant composed with ghost buttons in a toolbar.
- MultiSelector — Searchable: Multi-select with search filtering and select-all.
- MultiSelector — Sectioned Permissions: Multi-select with options grouped into labeled sections.

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
