{
  "name": "MultiSelect",
  "source": "src/components/MultiSelect.tsx",
  "propsType": "MultiSelectProps",
  "description": "MultiSelect — a text field that filters a list, with any number of selected\nvalues shown as removable tags beneath it.\n\nWHY IT IS ITS OWN COMPONENT, NOT `Combobox selectionMode=\"multiple\"`\n\nThe two differ in what the field holds. Combobox's input shows the chosen\nlabel — typing edits the value. Here the input only ever holds the filter\ntext, the value lives in the tags, and choosing an option leaves the list\nopen for the next one. One prop switching the meaning of the text in the\nbox is the kind of contract agents get wrong in generated code.\n\nWHAT REACT ARIA GIVES IT\n\n`useComboBoxState` and `useComboBox` in `selectionMode: 'multiple'`: the\nlistbox is `aria-multiselectable`, Enter and click toggle an option without\nclosing the list, the filter text clears after each pick, and native\n`required` is set only while nothing is chosen — \"at least one\", the same\nrule CheckboxGroup enforces.\n\nWHAT IT ADDS\n\n  - The tags. A TagGroup under the field, named by the field's label, one\n    tab stop, Delete or the × to remove. The selection must be visible with\n    the list closed, and \"3 selected\" hides which three.\n  - The value is announced with the field. React Aria points the input's\n    `aria-describedby` at a value element; this fills it with the chosen\n    labels joined by `Intl.ListFormat`, so \"Billing, Legal and Ops\" is read\n    in the user's language with no string shipped for it.\n  - Backspace in an empty field removes the last value, which is what\n    every tag input on the web has taught people to expect.\n  - Focus lands back in the field when the last tag is removed, instead of\n    falling to <body> with the TagGroup that held it.",
  "import": "import { MultiSelect } from 'ionbase-ui';",
  "status": "stable",
  "summary": "A text field that filters a list, with any number of values chosen. The choices show as removable tags beneath the field and are read with it.",
  "useWhen": [
    "the user picks several values from a list too long to show as checkboxes — teams, regions, labels, assignees",
    "a table filter can match more than one value — \"Status is Failing or Paused\""
  ],
  "useInstead": [
    {
      "when": "exactly one value is picked",
      "use": "Combobox",
      "why": "Combobox's input shows the chosen label; here the input only ever holds the filter text, and the list stays open for the next pick"
    },
    {
      "when": "there are about seven options or fewer",
      "use": "CheckboxGroup",
      "why": "every option visible at once beats typing to find one, and nothing is hidden behind a popover"
    },
    {
      "when": "the values are free text the user invents, not options from a list",
      "use": "TagGroup",
      "why": "MultiSelect only chooses from `options`; a free-text tag input is a TagGroup beside an Input"
    }
  ],
  "composition": {
    "order": [
      "MultiSelect"
    ],
    "note": "Pass the full `options` list; filtering, the tags and the announcement are the component's. In a table's filter bar, give it `aria-label` and `hideTags`, and list its values in the table's active-filters TagGroup — one removable tag per value, not the same tags twice.",
    "example": "<MultiSelect label=\"Notify teams\" options={teams} value={teamIds} onChange={setTeamIds} placeholder=\"Search teams\" />"
  },
  "variants": {
    "size": {
      "sm": {
        "use": "dense forms, table filters"
      },
      "md": {
        "use": "the default"
      },
      "lg": {
        "use": "prominent single-field forms"
      }
    }
  },
  "slots": {
    "label": {
      "accepts": "text",
      "note": "names the field and the tags beneath it"
    },
    "description": {
      "accepts": "text",
      "note": "replaced by `errorMessage` while `isInvalid` is set"
    },
    "errorMessage": {
      "accepts": "text",
      "note": "shown only while `isInvalid` is set"
    },
    "emptyLabel": {
      "accepts": "text",
      "note": "shown in the list when the filter matches nothing"
    }
  },
  "a11y": {
    "role": "combobox, controlling a listbox that is aria-multiselectable",
    "guarantees": [
      "React Aria's multi-select combobox: arrow keys move through the list while focus stays in the input, Enter or click toggles an option, and the list stays open for the next pick",
      "the chosen labels are on the input's `aria-describedby`, joined by `Intl.ListFormat` — read with the field in the user's language, with no string shipped for it",
      "the chosen values are a TagGroup named by the field's label: one tab stop, arrow keys between tags, Delete or the × to remove",
      "Backspace in an empty field removes the last value; with text in the field it only edits the text",
      "removing the last tag returns focus to the field instead of dropping it on <body>",
      "`isRequired` marks the field required only while nothing is chosen — at least one"
    ],
    "requires": [
      "`label`, or `aria-label` when there is no visible label — it names the tags as well as the field"
    ],
    "notes": [
      "While the list is open, React Aria hides everything outside the field and the list from assistive technology, the tags included. That is the platform pattern for a combobox, not a defect: the chosen options are marked selected in the list itself."
    ]
  },
  "antiPatterns": [
    {
      "dont": "a Combobox that appends each pick to a TagGroup by hand",
      "why": "the list closes after every pick, the input shows the last label, and nothing announces what is chosen"
    },
    {
      "dont": "a native <select multiple>",
      "why": "it needs Ctrl or Cmd to pick a second option, which most people never discover, and it cannot filter"
    },
    {
      "dont": "a count in place of the values — \"3 selected\"",
      "why": "it hides which three; the tags show them and the field reads them"
    },
    {
      "dont": "`hideTags` with nowhere else showing the values",
      "why": "the field shows only the filter text, so the choice is invisible until the list is opened"
    }
  ],
  "stylesheet": "src/styles/multi-select.css",
  "tokens": [
    "--border-disabled",
    "--border-primary-strong",
    "--border-stronger",
    "--border-width-default",
    "--border-width-thick",
    "--font-weight-regular",
    "--icon-on-color",
    "--ion-shadow-raised-flush-xs",
    "--radius-xs",
    "--spacing-16",
    "--spacing-2",
    "--spacing-4",
    "--spacing-8",
    "--surface-default",
    "--surface-disabled",
    "--surface-hover",
    "--surface-primary",
    "--type-body-line-height"
  ],
  "props": {
    "options": {
      "type": "readonly ComboboxOption[]",
      "required": true,
      "origin": "own",
      "description": "The full option list. Filtering happens here, against what is typed."
    },
    "label": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Field label. Required for a usable control — see `a11y.requires`."
    },
    "aria-label": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Names the field, and its tags, when there is no visible `label`."
    },
    "description": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Helper text below the field."
    },
    "errorMessage": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Replaces the helper text when `isInvalid` is set."
    },
    "isInvalid": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isDisabled": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isReadOnly": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isRequired": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "At least one value must be chosen."
    },
    "size": {
      "type": "MultiSelectSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Matches Input's `Size` variant: Small, Medium, Large.",
      "values": [
        "sm",
        "md",
        "lg"
      ]
    },
    "placeholder": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "value": {
      "type": "readonly string[] | undefined",
      "required": false,
      "origin": "own",
      "description": "The selected values (controlled), in the order they were chosen."
    },
    "defaultValue": {
      "type": "readonly string[] | undefined",
      "required": false,
      "origin": "own",
      "description": "The initially selected values (uncontrolled)."
    },
    "onChange": {
      "type": "((value: string[]) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Receives the whole new selection."
    },
    "inputValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Controlled filter text. Usually only needed for remote filtering."
    },
    "onInputChange": {
      "type": "((value: string) => void) | undefined",
      "required": false,
      "origin": "own"
    },
    "hideTags": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "Leave the tags out, when the chosen values are already on screen as\nremovable tags — a table's active-filters row. The values are still read\nwith the field, and still marked in the list."
    },
    "emptyLabel": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Shown in place of the list when nothing matches."
    },
    "buttonLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Accessible label for the disclosure button."
    },
    "name": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Posts every selected value under this name, for an uncontrolled form."
    },
    "className": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Class names for the field box (`.ion-input`)."
    },
    "wrapperClassName": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Class names for the `.ion-field` wrapper."
    },
    "id": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    }
  },
  "propCounts": {
    "own": 23,
    "aria": 0,
    "dom": 0,
    "other": 0
  }
}
