---
title: Select
---
The QSelect component has two types of selection: single or multiple. This component opens up a menu for the selection list and action. A filter can also be used for longer lists.

In case you are looking for a dropdown "button" instead of "input" use [Button Dropdown](button-dropdown.md) instead.

## QSelect API

### Props

- `hover` (boolean, optional) *(added v2.28)*
  Also opens the options menu when the pointer hovers the control and closes it when the pointer leaves both the control and the menu; Click/tap and keyboard interactions keep toggling as usual (touch devices fall back to them), so clicking the control closes a hover-shown menu, unless it is still animating into view (in which case the click focuses the select and keeps the menu, so a single move-and-click gesture cannot close what it just opened); A hover-triggered open does not focus the select (and thus emits no @focus/@blur), until the user actually clicks or tabs into it; Only applies when the select shows its options as a menu (so not with 'behavior=dialog', nor on mobile platforms without 'behavior=menu')
- `hover-delay` (number, optional), default `0` *(added v2.28)*
  Delay (in milliseconds) between the pointer entering the control and the options showing up; Requires the 'hover' prop
- `hover-hide-delay` (number, optional), default `150` *(added v2.28)*
  Grace period (in milliseconds) in which the pointer can re-enter the control or the options menu before it gets closed; Requires the 'hover' prop
- `name` (string, optional)
  Used to specify the name of the control; Useful if dealing with forms; If not specified, it takes the value of 'for' prop, if it exists
  Examples: `'car_id'`
- `virtual-scroll-horizontal` (boolean, optional)
  Make virtual list work in horizontal mode
- `virtual-scroll-slice-size` (number | string, optional), default `10`
  Minimum number of items to render in the virtual list
  Examples: `60`, `'60'`
- `virtual-scroll-slice-ratio-before` (number | string, optional), default `1`
  Ratio of number of items in visible zone to render before it
- `virtual-scroll-slice-ratio-after` (number | string, optional), default `1`
  Ratio of number of items in visible zone to render after it
- `virtual-scroll-item-size` (number | string, optional), default `24`
  Default size in pixels (height if vertical, width if horizontal) of an item; This value is used for rendering the initial list; Try to use a value close to the minimum size of an item
- `virtual-scroll-sticky-size-start` (number | string, optional), default `0`
  Size in pixels (height if vertical, width if horizontal) of the sticky part (if using one) at the start of the list; A correct value will improve scroll precision
- `virtual-scroll-sticky-size-end` (number | string, optional), default `0`
  Size in pixels (height if vertical, width if horizontal) of the sticky part (if using one) at the end of the list; A correct value will improve scroll precision
- `table-colspan` (number | string, optional)
  The number of columns in the table (you need this if you use table-layout: fixed)
- `model-value` (any, required, syncable)
  Model of the component; Must be Array if using 'multiple' prop; Either use this property (along with a listener for 'update:modelValue' event) OR use v-model directive
  Examples: `v-model="myModel"`
- `error` (boolean, optional), default `null`
  Does field have validation errors?; Setting it to a Boolean (even false) reserves the bottom space for the error message so the layout does not shift when the error appears; leave it at null (the default) when not using external validation, or use 'hide-bottom-space' to not reserve the space
- `error-message` (string, optional)
  Validation error message (gets displayed only if 'error' is set to 'true')
  Examples: `'Username must have at least 5 characters'`
- `no-error-icon` (boolean, optional)
  Hide error icon when there is an error
- `rules` (any[], optional)
  Array of Functions/Strings; If String, then it must be a name of one of the embedded validation rules
  Examples:
    - `[val => val.length <= 3 || 'Please use maximum 3 characters']`
    - `['fulltime']`
    - `[(val, rules) => rules.email(val) || 'Please enter a valid email address']`
- `reactive-rules` (boolean, optional)
  By default a change in the rules does not trigger a new validation until the model changes; If set to true then a change in the rules will trigger a validation; Has a performance penalty, so use it only when you really need it
- `lazy-rules` (boolean | string, optional), default `false`
  If set to boolean true then it checks validation status against the 'rules' only when field loses focus; while an error is displayed it re-checks on each model change so the error clears as soon as the value becomes valid; If set to 'ondemand' then it will trigger only when component's validate() method is manually called or when the wrapper QForm submits itself
  Accepts: `true`, `false`, `'ondemand'`
- `label` (string, optional)
  A text label that will “float” up above the input field, once the field gets focus
  Examples: `'Username'`
- `stack-label` (boolean, optional)
  Label will be always shown above the field regardless of field content (if any)
- `hint` (string, optional)
  Helper (hint) text which gets placed below your wrapped form component
  Examples: `'Fill in between 3 and 12 characters'`
- `hide-hint` (boolean, optional)
  Hide the helper (hint) text when field doesn't have focus
- `prefix` (string, optional)
  Prefix
  Examples: `'$'`
- `suffix` (string, optional)
  Suffix
  Examples: `'@gmail.com'`
- `label-color` (string, optional)
  Color name for the label from the Quasar Color Palette; Overrides the 'color' prop; The difference from 'color' prop is that the label will always have this color, even when field is not focused
  Examples: `'primary'`, `'teal'`, `'teal-10'`
- `color` (string, optional)
  Color name for component from the Quasar Color Palette
  Examples: `'primary'`, `'teal'`, `'teal-10'`
- `bg-color` (string, optional)
  Color name for component from the Quasar Color Palette
  Examples: `'primary'`, `'teal'`, `'teal-10'`
- `dark` (boolean, optional), default `null`
  Notify the component that the background is a dark color
- `loading` (boolean, optional)
  Signals the user a process is in progress by displaying a spinner; Spinner can be customized by using the 'loading' slot.
- `clearable` (boolean, optional)
  Appends clearable icon when a value (not undefined or null) is set; When clicked, model becomes null
- `clear-icon` (string, optional)
  Custom icon to use for the clear button when using along with 'clearable' prop
  Examples: `'close'`
- `filled` (boolean, optional)
  Use 'filled' design for the field
- `outlined` (boolean, optional)
  Use 'outlined' design for the field
- `borderless` (boolean, optional)
  Use 'borderless' design for the field
- `standout` (boolean | string, optional)
  Use 'standout' design for the field; Specifies classes to be applied when focused (overriding default ones); a text color class in there also colors the content and the decorators
  Examples: `true`, `'bg-primary text-white'`
- `label-slot` (boolean, optional)
  Enables label slot; You need to set it to force use of the 'label' slot if the 'label' prop is not set
- `bottom-slots` (boolean, optional)
  Enables bottom slots ('error', 'hint', 'counter')
- `hide-bottom-space` (boolean, optional)
  Do not reserve space for hint/error/counter anymore when these are not used; As a result, it also disables the animation for those
- `counter` (boolean, optional)
  Show an automatic counter on bottom right
- `rounded` (boolean, optional)
  Applies a small standard border-radius for a squared shape of the component
- `square` (boolean, optional)
  Remove border-radius so borders are squared; Overrides 'rounded' prop
- `dense` (boolean, optional)
  Dense mode; occupies less space
- `item-aligned` (boolean, optional)
  Match inner content alignment to that of QItem
- `disable` (boolean, optional)
  Put component in disabled mode
- `readonly` (boolean, optional)
  Put component in readonly mode
- `autofocus` (boolean, optional)
  Focus field on initial component render
- `for` (string, optional)
  Used to specify the 'id' of the control and also the 'for' attribute of the label that wraps it; If no 'name' prop is specified, then it is used for this attribute as well
  Examples: `'myFieldsId'`
- `multiple` (boolean, optional)
  Allow multiple selection; Model must be Array
- `display-value` (number | string, optional)
  Override default selection string, if not using 'selected' slot/scoped slot and if not using 'use-chips' prop
  Examples:
    - `'Options: x, y, z'`
- `display-value-html` (boolean, optional)
  Force render the selected option(s) as HTML; This can lead to XSS attacks so make sure that you sanitize the content; Does NOT apply when using 'selected' or 'selected-item' slots!
- `options` (any[], optional), default `[]`
  Array of objects with available options that the user can select from. For best performance freeze the list of options. Canonical form of each object is with 'label' (String), 'value' (Any) and optional 'disable' (Boolean) props (can be customized with options-value/option-label/option-disable props).
  Examples:
    - `['Tesla', 'iPhone']`
    - `[{ label: 'Tesla', value: 'car' }, { label: 'iPhone', value: 'phone' }]`
- `option-value` (Function | string, optional), default `'value'`
  Property of option which holds the 'value'; If using a function then for best performance, reference it from your scope and do not define it inline
  Function signature: `(option?: string | object) => any`
  Examples: `'modelNumber'`, `item => (item === null ? null : item.modelNumber)`
  Params:
    - `option` (string | object, optional)
      The current option being processed
      Examples:
        - `'Tesla'`
        - `'iPhone'`
        - `{ label: 'Tesla', value: 'car', cannotSelect: true }`
  Returns: `any`
    Value of the current option
    Examples: `'car'`, `34`
- `option-label` (Function | string, optional), default `'label'`
  Property of option which holds the 'label'; If using a function then for best performance, reference it from your scope and do not define it inline
  Function signature: `(option?: string | object) => string`
  Examples: `'itemName'`, `item => (item === null ? 'Null value' : item.itemName)`
  Params:
    - `option` (string | object, optional)
      The current option being processed
      Examples:
        - `'Tesla'`
        - `'iPhone'`
        - `{ label: 'Tesla', value: 'car', cannotSelect: true }`
  Returns: `string`
    Label of the current option
    Examples: `'Tesla'`, `'iPhone'`
- `option-disable` (Function | string, optional), default `'disable'`
  Property of option which tells it's disabled; The value of the property must be a Boolean; If using a function then for best performance, reference it from your scope and do not define it inline
  Function signature: `(option?: string | object) => boolean`
  Examples: `item => (item === null ? true : item.cannotSelect)`, `option-disable="cannotSelect"`
  Params:
    - `option` (string | object, optional)
      The current option being processed
      Examples:
        - `'Tesla'`
        - `'iPhone'`
        - `{ label: 'Tesla', value: 'car', cannotSelect: true }`
  Returns: `boolean`
    If true, the current option will be disabled
- `hide-selected` (boolean, optional)
  Hides selection; Use the underlying input tag to hold the label (instead of showing it to the right of the input) of the selected option; Only works for non 'multiple' Selects
- `hide-dropdown-icon` (boolean, optional)
  Hides dropdown icon; the default loading spinner is also suppressed (with the icon hidden it would change the field's width), while a 'loading' slot still renders
- `hide-dialog-close` (boolean, optional) *(added v2.28)*
  Hide the close button rendered inside the control when the options are displayed as a dialog
- `dropdown-icon` (string, optional)
  Icon name following Quasar convention; Make sure you have the icon library installed unless you are using 'img:' prefix; If 'none' (String) is used as value then no icon is rendered (but screen real estate will still be used for it)
  Examples: `'map'`, `'ion-add'`, `'img:https://cdn.quasar.dev/logo-v2/svg/logo.svg'`, `'img:path/to/some_image.png'`
- `max-values` (number | string, optional)
  Allow a maximum number of selections that the user can do
- `options-dense` (boolean, optional)
  Dense mode for options list; occupies less space
- `options-dark` (boolean, optional), default `null`
  Options menu will be colored with a dark color
- `options-selected-class` (string, optional)
  CSS class name for options that are active/selected; Set it to an empty string to stop applying the default (which is text-* where * is the 'color' prop value)
  Examples: `'text-orange'`
- `options-html` (boolean, optional)
  Force render the options as HTML; This can lead to XSS attacks so make sure that you sanitize the content; Does NOT apply when using 'option' slot!
- `options-cover` (boolean, optional)
  Expanded menu will cover the component (will not work along with 'use-input' prop for obvious reasons)
- `no-option-label` (string, optional) *(added v2.28)*
  Message to display in the menu when there are no options to show; Gets overridden by the 'no-option' slot, which should be used instead when custom content or per-instance markup is needed
  Examples: `'No results'`
- `menu-shrink` (boolean, optional)
  Allow the options list to be narrower than the field (only in menu mode)
- `popup-content-class` (string, optional)
  Class definitions to be attributed to the popup content
  Examples: `'my-special-class'`
- `popup-content-style` (string | any[] | object, optional)
  Style definitions to be attributed to the popup content
  Examples: `'background-color: #ff0000'`, `{ backgroundColor: '#ff0000' }`
- `popup-no-route-dismiss` (boolean, optional) *(added v2.15)*
  Changing route app won't dismiss the popup (menu or dialog)
- `use-chips` (boolean, optional)
  Use QChip to show what is currently selected
- `no-chip-remove` (boolean, optional) *(added v2.28)*
  Hide the remove icon of the QChips (when 'use-chips' is enabled) and disable removing the last selection with Backspace; Does not affect 'clearable' behavior
- `use-input` (boolean, optional)
  Use an input tag where users can type
- `maxlength` (string | number, optional)
  Specify a max length for the inner input tag (if 'use-input' is enabled)
- `fill-input` (boolean, optional)
  Fills the input with current value; Useful along with 'hide-selected'; Does NOT work along with 'multiple' selection
- `new-value-mode` (string, optional)
  Enables creation of new values and defines behavior when a new value is added: 'add' means it adds the value (even if possible duplicate), 'add-unique' adds only unique values, and 'toggle' adds or removes the value (based on if it exists or not already); When using this prop then listening for @new-value becomes optional (only to override the behavior defined by 'new-value-mode')
  Accepts: `'add'`, `'add-unique'`, `'toggle'`
- `map-options` (boolean, optional)
  Try to map labels of model from 'options' Array; has a small performance penalty; If you are using emit-value you will probably need to use map-options to display the label text in the select field rather than the value;  Refer to the 'Affecting model' section above
- `no-option-prefetch` (boolean, optional) *(added v2.28)*
  Do not automatically call the 'filter' handler when the (lazy loaded) options are needed to map a preloaded model value through 'map-options'; the field displays the raw model value until the options get loaded
- `disable-tab-selection` (boolean, optional) *(added v2.17)*
  Prevents the tab key from confirming the currently hovered option
- `emit-value` (boolean, optional)
  Update model with the value of the selected option instead of the whole option
- `input-debounce` (number | string, optional), default `500`
  Debounce the input model update with an amount of milliseconds (also affects the 'filter' event, if used)
- `input-class` (string | any[] | object, optional)
  Class definitions to be attributed to the underlying input tag
  Examples: `'my-special-class'`, `{ 'my-special-class': true }`
- `input-style` (string | any[] | object, optional)
  Style definitions to be attributed to the underlying input tag
  Examples: `'background-color: #ff0000'`, `{ backgroundColor: '#ff0000' }`
- `tabindex` (number | string, optional), default `0`
  Tabindex HTML attribute value
  Examples: `100`, `'0'`
- `autocomplete` (string, optional)
  Autocomplete attribute for field
  Examples: `'country'`
- `transition-show` (string, optional), default `'fade'`
  Transition when showing the menu/dialog; One of Quasar's embedded transitions
  Examples: `'fade'`, `'slide-down'`
- `transition-hide` (string, optional), default `'fade'`
  Transition when hiding the menu/dialog; One of Quasar's embedded transitions
  Examples: `'fade'`, `'slide-down'`
- `transition-duration` (string | number, optional), default `300`
  Transition duration when hiding the menu/dialog (in milliseconds, without unit)
- `behavior` (string, optional), default `'default'`
  Overrides the default dynamic mode of showing as menu on desktop and dialog on mobiles
  Accepts: `'default'`, `'menu'`, `'dialog'`
- `menu-anchor` (string, optional)
  Two values setting the starting position or anchor point of the options list relative to the field (only in menu mode)
  Accepts: `'top left'`, `'top middle'`, `'top right'`, `'top start'`, `'top end'`, `'center left'`, `'center middle'`, `'center right'`, `'center start'`, `'center end'`, `'bottom left'`, `'bottom middle'`, `'bottom right'`, `'bottom start'`, `'bottom end'`
- `menu-self` (string, optional)
  Two values setting the options list's own position relative to its target (only in menu mode)
  Accepts: `'top left'`, `'top middle'`, `'top right'`, `'top start'`, `'top end'`, `'center left'`, `'center middle'`, `'center right'`, `'center start'`, `'center end'`, `'bottom left'`, `'bottom middle'`, `'bottom right'`, `'bottom start'`, `'bottom end'`
- `menu-offset` (any[], optional)
  An array of two numbers (in pixels) which expands the anchor element's bounding box outward horizontally and vertically; the options list is then positioned against the expanded box, so the visible effect depends on the 'anchor'/'self' points in use (only in menu mode)
  Examples:
    - `[8, 8]`
    - `[5, 10]`

### Computed Props

- `hasError` (boolean, optional)
  Whether the component is in error state

### Methods

- `scrollTo(index: number | string, edge?: string): void`
  Scroll the virtual scroll list to the item with the specified index (0 based)
  Params:
    - `index` (number | string, required)
      The index of the list item (0 based)
    - `edge` (string, optional)
      The edge to align to if the item is not visible already (by default it aligns to end if scrolling towards the end and to start otherwise); If the '-force' version is used then it always aligns
      Accepts: `'start'`, `'center'`, `'end'`, `'start-force'`, `'center-force'`, `'end-force'`
- `reset(): void`
  Resets the virtual scroll computations; Needed for custom edge-cases
- `refresh(index?: string | number): void`
  Refreshes the virtual scroll list; Use it after appending items
  Params:
    - `index` (string | number, optional)
      The index of the list item to scroll to after refresh (0 based); If it's not specified the scroll position is not changed; Use a negative value to keep scroll position
      Examples: `5`
- `resetValidation(): void`
  Reset validation status
- `validate(value?: any): boolean | Promise<boolean>`
  Trigger a validation
  Params:
    - `value` (any, optional)
      Optional value to validate against
  Returns: `boolean | Promise<boolean>`
    True/false if no async rules, otherwise a Promise with the outcome (true -> validation was a success, false -> invalid models detected)
    Examples: `true`, `validate().then(outcome => { ... })`
- `focus(): void`
  Focus component
- `blur(): void`
  Blur component (lose focus)
- `showPopup(): void`
  Focus and open popup
- `hidePopup(): void`
  Hide popup
- `removeAtIndex(index: number): void`
  Remove selected option located at specific index
  Params:
    - `index` (number, required)
      Index at which to remove selection
- `add(opt: any, unique?: boolean): void`
  Adds option to model
  Params:
    - `opt` (any, required)
      Option to add to model
    - `unique` (boolean, optional)
      Option must be unique
- `toggleOption(opt: any, keepOpen?: boolean): void`
  Add/remove option from model
  Params:
    - `opt` (any, required)
      Option to add to model
    - `keepOpen` (boolean, optional)
      Don't close the menu and do not clear the filter
- `getOptionIndex(): number`
  Gets current focused option index from menu; It's -1 if no option is focused
  Returns: `number`
    Index of option from menu; It's -1 if no option is focused
- `setOptionIndex(index: number): void`
  Sets option from menu as 'focused'; -1 to focus none
  Params:
    - `index` (number, required)
      Index of option from menu; -1 to focus none
- `moveOptionSelection(offset?: number, skipInputValue?: boolean): void`
  Move selected option from menu by index offset
  Params:
    - `offset` (number, optional), default `1`
      Number of options to move up or down
    - `skipInputValue` (boolean, optional)
      Don't set input-value on navigation
- `filter(value: string): void`
  Filter options
  Params:
    - `value` (string, required)
      String to filter with
      Examples: `'car'`
- `updateMenuPosition(): void`
  Recomputes menu position
- `updateInputValue(value: string, noFilter?: boolean): void`
  If 'use-input' is specified, this updates the value that it holds
  Params:
    - `value` (string, required)
      String to set the input value to
      Examples: `'hotel'`
    - `noFilter` (boolean, optional)
      Set to true if you don't want the filter (if any) to be also triggered
- `isOptionSelected(opt: any): boolean`
  Tells if an option is selected
  Params:
    - `opt` (any, required)
      Option entry
  Returns: `boolean`
    Option is selected or not
- `getEmittingOptionValue(opt: any): any`
  Get the model value that would be emitted by QSelect when selecting a said option; Also takes into consideration if 'emit-value' is set
  Params:
    - `opt` (any, required)
      Option entry
  Returns: `any`
    Emitting model value of said option
- `getOptionValue(opt: any): any`
  Get the model value of an option; Takes into consideration 'option-value' (if used), but does not looks for 'emit-value', like getEmittingOptionValue() does
  Params:
    - `opt` (any, required)
      Option entry
  Returns: `any`
    Model value of said option
- `getOptionLabel(opt: any): any`
  Get the label of an option; Takes into consideration the 'option-label' prop (if used)
  Params:
    - `opt` (any, required)
      Option entry
  Returns: `any`
    Label of said option
- `isOptionDisabled(opt: any): boolean`
  Tells if an option is disabled; Takes into consideration 'option-disable' prop (if used)
  Params:
    - `opt` (any, required)
      Option entry
  Returns: `boolean`
    Option is disabled or not

### Events

- `@virtual-scroll`
  Emitted when the virtual scroll occurs
  Params:
    - `details` (object, optional)
      Object of properties on the new scroll position
      Object shape:
        - `index` (number, required)
          Index of the list item that was scrolled into view (0 based)
        - `from` (number, required)
          The index of the first list item that is rendered (0 based)
        - `to` (number, required)
          The index of the last list item that is rendered (0 based)
        - `direction` (string, required)
          Direction of change
          Accepts: `'increase'`, `'decrease'`
        - `ref` (ComponentInstance, required)
          Vue reference to the QSelect
- `@update:model-value`
  Emitted when the component needs to change the model; Is also used by v-model
  Params:
    - `value` (any, required)
      New model value
- `@focus`
  Emitted when component gets focused
  Params:
    - `evt` (Event, optional)
      JS event object
- `@blur`
  Emitted when component loses focus
  Params:
    - `evt` (Event, optional)
      JS event object
- `@clear`
  When using the 'clearable' property, this event is emitted when the clear icon is clicked
  Params:
    - `value` (any, optional)
      The previous value before clearing it
- `@input-value`
  Emitted when the value in the text input changes
  Params:
    - `value` (string, optional)
      New text value
- `@remove`
  Emitted when an option is removed from selection
  Params:
    - `details` (object, optional)
      Removal details
      Object shape:
        - `index` (number, required)
          Model index at which removal took place
        - `value` (any, required)
          The actual value that was removed
- `@add`
  Emitted when an option is added to the selection
  Params:
    - `details` (object, optional)
      Addition details
      Object shape:
        - `index` (number, required)
          Model index at which addition took place
        - `value` (any, required)
          The actual value that was added
- `@new-value`
  Enables creation of new values; Emitted when a new value has been created; You can override 'new-value-mode' property with it
  Params:
    - `inputValue` (string, optional)
      What the user typed
      Examples: `'phone'`
    - `doneFn` (Function, optional)
      Adds (optional) value to the model; Do not forget to call it after you validate the newly created value; Call it with no parameters if nothing should be added
      Function signature: `(item?: any, mode?: string) => void`
      Params:
        - `item` (any, optional)
          Item to add
        - `mode` (string, optional), default `'add'`
          Override 'new-value-mode' (prop) behavior should you wish to
          Accepts: `'add'`, `'add-unique'`, `'toggle'`
- `@filter`
  Emitted when user wants to filter a value; Also emitted (with an empty input value) by QSelect itself when it needs the lazy loaded options to map a preloaded model value through 'map-options' (opt out with 'no-option-prefetch')
  Params:
    - `inputValue` (string, optional)
      What the user typed
      Examples: `'phone'`
    - `doneFn` (Function, optional)
      Supply a function which makes the necessary updates
      Function signature: `(callbackFn: Function, afterFn?: Function) => void`
      Params:
        - `callbackFn` (Function, required)
          Callback to call to make the actual updates
        - `afterFn` (Function, optional)
          Callback to call at the end after the update has been fully processed by QSelect
          Function signature: `(ref: ComponentInstance) => void`
          Params:
            - `ref` (ComponentInstance, required)
              Vue reference to the QSelect which triggered the filtering
    - `abortFn` (Function, optional)
      Call this function if something went wrong
- `@filter-abort`
  Emitted when a filtering was aborted; Probably a new one was requested?
- `@popup-show`
  Emitted when the select options menu or dialog is shown.
  Params:
    - `evt` (Event, optional)
      JS event object; Is undefined when the popup is not triggered by a direct user interaction (e.g. it opens as an async filter resolves or through the showPopup method)
- `@popup-hide`
  Emitted when the select options menu or dialog is hidden.
  Params:
    - `evt` (Event, optional)
      JS event object; Is undefined when the popup is not dismissed by a direct user interaction (e.g. it closes because an option got selected or through the hidePopup method)

### Slots

- `#default`
  Field main content
- `#prepend`
  Prepend inner field; Suggestions: QIcon, QBtn
- `#append`
  Append to inner field; Suggestions: QIcon, QBtn
- `#before`
  Prepend outer field; Suggestions: QIcon, QBtn
- `#after`
  Append outer field; Suggestions: QIcon, QBtn
- `#label`
  Slot for label; Used only if 'label-slot' prop is set or the 'label' prop is set; When it is used the text in the 'label' prop is ignored
- `#error`
  Slot for errors; Enabled only if 'bottom-slots' prop is used; Suggestion: <div>
- `#hint`
  Slot for hint text; Enabled only if 'bottom-slots' prop is used; Suggestion: <div>
- `#counter`
  Slot for counter text; Enabled only if 'bottom-slots' prop is used; Suggestion: <div>
- `#loading`
  Override default spinner when component is in loading mode; Suggestion: spinners
- `#selected`
  Override default selection slot; Suggestion: QChip
- `#before-options`
  Template slot for the elements that should be rendered before the list of options; the ARIA combobox pattern keeps keyboard focus on the field while the popup is open, so interactive elements placed here are not keyboard/screen-reader accessible - prefer modeling such actions as regular options or placing them outside of the popup
- `#after-options`
  Template slot for the elements that should be rendered after the list of options; the ARIA combobox pattern keeps keyboard focus on the field while the popup is open, so interactive elements placed here are not keyboard/screen-reader accessible - prefer modeling such actions as regular options or placing them outside of the popup

### Scoped Slots

- `#no-option`
  What should the menu display after filtering options and none are left to be displayed; Suggestion: <div>
  Scope:
    - `inputValue` (string, optional)
      Input textfield value, if any (not QSelect model)
      Examples: `'typed something'`
- `#selected-item`
  Override default selection slot; Suggestion: QChip
  Scope:
    - `index` (number, optional)
      Selection index
    - `opt` (any, optional)
      Selected option -- its value is taken from model
    - `selected` (boolean, optional)
      Always true -- passed down as prop to QItem (when using QItem)
    - `html` (boolean, optional)
      Is the content HTML?
    - `removeAtIndex` (Function, optional)
      Remove selected option located at specific index
      Function signature: `(index: number) => void`
      Params:
        - `index` (number, required)
          Index at which to remove selection
    - `toggleOption` (Function, optional)
      Add/remove option from model
      Function signature: `(opt: any) => void`
      Params:
        - `opt` (any, required)
          Option to add to model
    - `tabindex` (number, optional)
      Tabindex HTML attribute value associated with respective option
      Accepts: `0`, `-1`
- `#option`
  Customize how options are rendered; Suggestion: QItem
  Scope:
    - `index` (number, optional)
      Option index
    - `opt` (any, optional)
      Option -- its value is taken from 'options' prop
    - `html` (boolean, optional)
      Is the content HTML?
    - `label` (string, optional)
      Label of the option
    - `selected` (boolean, optional)
      Is option selected?
    - `focused` (boolean, optional)
      Is option focused?
    - `toggleOption` (Function, optional)
      Add/remove option from model
      Function signature: `(opt: any) => void`
      Params:
        - `opt` (any, required)
          Option to add to model
    - `setOptionIndex` (Function, optional)
      Sets option from menu as 'focused'
      Function signature: `(index: number) => void`
      Params:
        - `index` (number, required)
          Index of option from menu
    - `itemProps` (object, optional)
      Computed properties passed down to QItem

## Design

### Overview

> [!NOTE]
> For your QSelect you can use only one of the main designs (`filled`, `outlined`, `standout`, `borderless`). You cannot use multiple as they are self-exclusive.

Example "Design Overview":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-select v-model="model" :options="options" label="Standard" />

      <q-select filled v-model="model" :options="options" label="Filled" />

      <q-select outlined v-model="model" :options="options" label="Outlined" />

      <q-select standout v-model="model" :options="options" label="Standout" />

      <q-select
        standout="bg-teal text-white"
        v-model="model"
        :options="options"
        label="Custom standout"
      />

      <q-select
        borderless
        v-model="model"
        :options="options"
        label="Borderless"
      />

      <q-select
        rounded
        filled
        v-model="model"
        :options="options"
        label="Rounded filled"
      />

      <q-select
        rounded
        outlined
        v-model="model"
        :options="options"
        label="Rounded outlined"
      />

      <q-select
        rounded
        standout
        v-model="model"
        :options="options"
        label="Rounded standout"
      />

      <q-select
        square
        filled
        v-model="model"
        :options="options"
        label="Square filled"
      />

      <q-select
        square
        outlined
        v-model="model"
        :options="options"
        label="Square outlined"
      />

      <q-select
        square
        standout
        v-model="model"
        :options="options"
        label="Square standout"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Decorators

```vue
<template>
  <div class="q-pb-lg">
    <q-toggle v-model="dense" label="Dense QSelect" />
    <q-toggle v-model="denseOpts" label="Dense options" />
  </div>

  <div class="q-gutter-md" style="max-width: 300px">
    <q-select
      filled
      v-model="model"
      :options="options"
      label="Label (stacked)"
      stack-label
      :dense="dense"
      :options-dense="denseOpts"
    />

    <q-select
      outlined
      v-model="model"
      :options="options"
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #prepend>
        <q-icon name="event" />
      </template>
    </q-select>

    <q-select
      standout
      v-model="model"
      :options="options"
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #append>
        <q-avatar>
          <img
            alt="Quasar logo"
            src="https://cdn.quasar.dev/logo-v2/svg/logo.svg"
          />
        </q-avatar>
      </template>
    </q-select>

    <q-select
      filled
      bottom-slots
      v-model="model"
      :options="options"
      label="Label"
      counter
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #prepend>
        <q-icon name="place" @click.stop.prevent />
      </template>
      <template #append>
        <q-icon
          name="close"
          @click.stop.prevent="model = ''"
          class="cursor-pointer"
        />
      </template>

      <template #hint> Field hint </template>
    </q-select>

    <q-select
      rounded
      outlined
      bottom-slots
      v-model="model"
      :options="options"
      label="Label"
      counter
      maxlength="12"
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #before>
        <q-icon name="flight_takeoff" />
      </template>

      <template #append>
        <q-icon
          v-if="model !== ''"
          name="close"
          @click.stop.prevent="model = ''"
          class="cursor-pointer"
        />
        <q-icon name="search" @click.stop.prevent />
      </template>

      <template #hint> Field hint </template>
    </q-select>

    <q-select
      filled
      bottom-slots
      v-model="model"
      :options="options"
      label="Label"
      counter
      maxlength="12"
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #before>
        <q-avatar>
          <img
            alt="User avatar"
            src="https://cdn.quasar.dev/img/avatar5.jpg"
          />
        </q-avatar>
      </template>

      <template #append>
        <q-icon
          v-if="model !== ''"
          name="close"
          @click.stop.prevent="model = ''"
          class="cursor-pointer"
        />
        <q-icon name="schedule" @click.stop.prevent />
      </template>

      <template #hint> Field hint </template>

      <template #after>
        <q-btn round dense flat icon="send" />
      </template>
    </q-select>

    <q-select
      filled
      bottom-slots
      v-model="model"
      :options="options"
      label="Label"
      counter
      maxlength="12"
      :dense="dense"
      :options-dense="denseOpts"
    >
      <template #before>
        <q-icon name="event" />
      </template>

      <template #hint> Field hint </template>

      <template #append>
        <q-btn round dense flat icon="add" @click.stop.prevent />
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
const dense = ref(false)
const denseOpts = ref(false)
</script>
```

### Coloring

```vue
<template>
  <div class="q-gutter-y-md column" style="max-width: 300px">
    <q-select
      color="purple-12"
      v-model="model"
      :options="options"
      label="Label"
    >
      <template #prepend>
        <q-icon name="event" />
      </template>
    </q-select>

    <q-select
      color="teal"
      filled
      v-model="model"
      :options="options"
      label="Label"
    >
      <template #prepend>
        <q-icon name="event" />
      </template>
    </q-select>

    <q-select
      color="grey-3"
      outlined
      label-color="orange"
      v-model="model"
      :options="options"
      label="Label"
    >
      <template #append>
        <q-icon name="event" color="orange" />
      </template>
    </q-select>

    <q-select
      color="lime-11"
      bg-color="green"
      filled
      v-model="model"
      :options="options"
      label="Label"
    >
      <template #prepend>
        <q-icon name="event" />
      </template>
    </q-select>

    <q-select
      color="teal"
      outlined
      v-model="model"
      :options="options"
      label="Label"
    >
      <template #append>
        <q-avatar>
          <img
            alt="Quasar logo"
            src="https://cdn.quasar.dev/logo-v2/svg/logo.svg"
          />
        </q-avatar>
      </template>
    </q-select>

    <q-select
      clearable
      color="orange"
      standout
      bottom-slots
      v-model="model"
      :options="options"
      label="Label"
      counter
    >
      <template #prepend>
        <q-icon name="place" />
      </template>
      <template #append>
        <q-icon name="favorite" />
      </template>

      <template #hint> Field hint </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Clearable

As a helper, you can use `clearable` prop so user can reset model to `null` through an appended icon. The second QSelect in the example below is the equivalent of using `clearable`.

```vue
<template>
  <div class="q-gutter-y-md column" style="max-width: 300px">
    <q-select
      clearable
      filled
      color="purple-12"
      v-model="model"
      :options="options"
      label="Label"
    />

    <!-- equivalent -->
    <q-select
      color="orange"
      filled
      v-model="model"
      :options="options"
      label="Label"
    >
      <template v-if="model" #append>
        <q-icon
          name="cancel"
          @click.stop.prevent="model = null"
          class="cursor-pointer"
        />
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref('Google')
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Disable and readonly

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      disable
      filled
      v-model="model"
      :options="options"
      hint="Disable"
      style="width: 250px"
    />

    <q-select
      readonly
      filled
      v-model="model"
      :options="options"
      hint="Readonly"
      style="width: 250px"
    />

    <q-select
      disable
      readonly
      filled
      v-model="model"
      :options="options"
      hint="Disable and readonly"
      style="width: 250px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref('Google')
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Slots with QBtn type "submit"

> [!IMPORTANT]
> When placing a QBtn with type "submit" in one of the "before", "after", "prepend", or "append" slots of a QField, QInput or QSelect, you should also add a `@click` listener on the QBtn in question. This listener should call the method that submits your form. All "click" events in such slots are not propagated to their parent elements.

### Menu transitions

> [!NOTE]
> Please note that transitions do not work when using `options-cover` prop.

In the example below there's a few transitions showcased. For a full list of transitions available, go to [Transitions](../options/transitions.md).

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      label="Flip up/down"
      transition-show="flip-up"
      transition-hide="flip-down"
      filled
      v-model="model"
      :options="options"
      style="width: 250px"
    />

    <q-select
      label="Scale"
      transition-show="scale"
      transition-hide="scale"
      filled
      v-model="model"
      :options="options"
      style="width: 250px"
    />

    <q-select
      label="Jump up"
      transition-show="jump-up"
      transition-hide="jump-up"
      filled
      v-model="model"
      :options="options"
      style="width: 250px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Options list display mode

By default QSelect shows the list of options as a menu on desktop and as a dialog on mobiles. You can force one behavior by using the `behavior` property.

The dialog mode renders a "Close" button (label taken from the [Quasar Language Pack](../options/quasar-language-packs.md)) inside the dialog's control, so users are not forced to tap on the backdrop in order to dismiss it. The button picks up the `color` prop, can be further styled through its `q-select__dialog-close` CSS class, or removed altogether with the `hide-dialog-close` prop.

> [!WARNING]
> Please note that on iOS menu behavior might generate problems, especially when used in combination with `use-input` prop. You can use a conditional `behavior` prop like `:behavior="$q.platform.is.ios ? 'dialog' : 'menu'"` to use dialog mode only on iOS.

Example "Show options in menu":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      label="Simple select"
      :options="stringOptions"
      style="width: 250px"
      behavior="menu"
    />

    <q-select
      filled
      v-model="model"
      use-input
      input-debounce="0"
      label="Simple filter"
      :options="options"
      @filter="filterFn"
      style="width: 250px"
      behavior="menu"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update) {
  if (val === '') {
    update(() => {
      options.value = stringOptions
    })
    return
  }

  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

Example "Show options in dialog":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      label="Simple select"
      :options="stringOptions"
      style="width: 250px"
      behavior="dialog"
    />

    <q-select
      filled
      v-model="model"
      use-input
      input-debounce="0"
      label="Simple filter"
      :options="options"
      @filter="filterFn"
      style="width: 250px"
      behavior="dialog"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update) {
  if (val === '') {
    update(() => {
      options.value = stringOptions
    })
    return
  }

  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

### Hover to open *(v2.28+)*

With the `hover` prop the options also open when the pointer hovers the select and close once the pointer has left both the select and its options menu. The `hover-hide-delay` prop controls the grace period in which the pointer can travel between the two (or return) before the options close, while `hover-delay` postpones the opening.

Click/tap and keyboard interactions keep toggling the options as usual, so touch devices (which have no hover) simply fall back to them; this also means that clicking a hover-opened select closes its options. The one exception is a click that lands while the options are still animating into view: it focuses the select and keeps them open, so a single move-and-click gesture cannot close what it just opened.

A hover-triggered open does not focus the select, so it emits no `@focus`/`@blur` and does not trigger lazy validation rules on a pointer merely passing over; the select only gets focused (upgrading the open to a regular one, which no longer closes when the pointer leaves) when the user actually clicks or tabs into it.

> [!NOTE]
> The prop only applies while the options show up as a menu; it has no effect with `behavior="dialog"`, nor on mobile platforms unless `behavior="menu"` is used.

Example "Hover to open":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      hover
      label="Hover to open"
      :options="stringOptions"
      style="width: 250px"
    />

    <q-select
      filled
      v-model="model"
      hover
      :hover-delay="300"
      :hover-hide-delay="600"
      label="With delays"
      :options="stringOptions"
      style="width: 250px"
    />

    <q-select
      filled
      v-model="model"
      hover
      use-input
      input-debounce="0"
      label="With filtering"
      :options="options"
      @filter="filterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update) {
  if (val === '') {
    update(() => {
      options.value = stringOptions
    })
    return
  }

  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

## The model

> [!IMPORTANT]
> The model for single selection can be anything (String, Object, ...) while the model for multiple selection must be an Array.

Example "Single vs multiple selection":

```vue
<template>
  <div class="q-gutter-md row items-start">
    <q-select
      filled
      v-model="single"
      :options="options"
      label="Single"
      style="width: 250px"
    />

    <q-select
      filled
      v-model="multiple"
      multiple
      :options="options"
      label="Multiple"
      style="width: 250px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const single = ref(null)
const multiple = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

Example "Multiple selection, counter and max-values":

```vue
<template>
  <div class="q-gutter-md row items-start">
    <q-select
      filled
      v-model="model"
      multiple
      :options="options"
      counter
      hint="With counter"
      style="width: 250px"
    />

    <q-select
      filled
      v-model="model2"
      multiple
      :options="options"
      counter
      max-values="2"
      hint="Max 2 selections"
      style="width: 250px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const model2 = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

The model content can be influenced by `emit-value` prop as you'll learn in "The options" section below.

## The options

### Options type

Example "String options":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select filled v-model="model" :options="options" label="Standard" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

Example "Object options":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select filled v-model="model" :options="options" label="Standard" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = [
  {
    label: 'Google',
    value: 'Google',
    description: 'Search engine',
    category: '1'
  },
  // ...
  {
    label: 'Oracle',
    value: 'Oracle',
    disable: true,
    description: 'Databases',
    category: '3'
  }
]
</script>
```

### Affecting model

When `emit-value` is used, the model becomes the determined `value` from the specified selected option. Default is to emit the whole option. It makes sense to use it only when the options are of Object form.

Example "Emit-value":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        label="Standard"
        emit-value
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = [
  {
    label: 'Google',
    value: 'goog',
    description: 'Search engine',
    icon: 'mail'
  },
  // ...
  {
    label: 'Oracle',
    value: 'ora',
    disable: true,
    description: 'Databases',
    icon: 'casino'
  }
]
</script>
```

When `map-options` is used, the model can contain only the `value`, and it will be mapped against the options to determine its label. There is a performance penalty involved, so use it only if absolutely necessary. It's not needed, for example, if the model contains the whole Object (so contains the label prop).

Example "Map options":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        label="Standard"
        emit-value
        map-options
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = [
  {
    label: 'Google',
    value: 'goog'
  },
  // ...
  {
    label: 'Oracle',
    value: 'ora',
    disable: true
  }
]
</script>
```

### Custom prop names

By default, QSelect looks at `label`, `value`, `disable` and `sanitize` props of each option from the options array Objects. But you can override those:

> [!WARNING]
> If you use functions for custom props always check if the option is null. These functions are used both for options in the list and for the selected options.

Example "Custom label, value and disable props":

```vue
<template>
  <div class="q-gutter-md row items-start">
    <div class="col-12">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>
    </div>

    <q-select
      filled
      v-model="model"
      :options="options"
      option-value="id"
      option-label="desc"
      option-disable="inactive"
      emit-value
      map-options
      style="min-width: 250px; max-width: 300px"
    />

    <q-select
      filled
      v-model="model"
      :options="options"
      :option-value="
        opt => (Object(opt) === opt && 'id' in opt ? opt.id : null)
      "
      :option-label="
        opt => (Object(opt) === opt && 'desc' in opt ? opt.desc : '- Null -')
      "
      :option-disable="
        opt => (Object(opt) === opt ? opt.inactive === true : true)
      "
      emit-value
      map-options
      style="min-width: 250px; max-width: 300px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = [
  {
    id: 'goog',
    desc: 'Google'
  },
  // ...
  {
    id: 'ora',
    desc: 'Oracle',
    inactive: true
  }
]
</script>
```

### Customizing menu options

> [!IMPORTANT]
> The list of options is rendered using virtual scroll, so if you render more than one element for an option you must set a `q-virtual-scroll--with-prev` class on all elements except the first one.

Example "Options slot":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        label="Standard"
        color="teal"
        clearable
        options-selected-class="text-deep-orange"
      >
        <template #option="scope">
          <q-item v-bind="scope.itemProps">
            <q-item-section avatar>
              <q-icon :name="scope.opt.icon" />
            </q-item-section>
            <q-item-section>
              <q-item-label>{{ scope.opt.label }}</q-item-label>
              <q-item-label caption>{{ scope.opt.description }}</q-item-label>
            </q-item-section>
          </q-item>
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = [
  {
    label: 'Google',
    value: 'Google',
    description: 'Search engine',
    icon: 'mail'
  },
  // ...
  {
    label: 'Oracle',
    value: 'Oracle',
    disable: true,
    description: 'Databases',
    icon: 'casino'
  }
]
</script>
```

Here is another example where we add a QToggle to each option. The possibilities are endless.

Example "Object options":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        label="Multi with toggle"
        multiple
        emit-value
        map-options
      >
        <template #option="{ itemProps, opt, selected, toggleOption }">
          <q-item v-bind="itemProps">
            <q-item-section>
              <q-item-label v-html="opt.label" />
            </q-item-section>
            <q-item-section side>
              <q-toggle
                :model-value="selected"
                @update:model-value="toggleOption(opt)"
              />
            </q-item-section>
          </q-item>
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref([])
const options = [
  {
    label: 'Google',
    value: 1
  },
  // ...
]
</script>
```

By default, when there are no options, the menu won't appear. But you can customize this scenario and specify what the menu should display. For a plain text message, the `no-option-label` prop (v2.28+) is enough; use the `no-option` slot (it overrides the prop) when you need custom content:

Example "No options slot":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-select filled v-model="model" :options="options" label="No options">
        <template #no-option>
          <q-item>
            <q-item-section class="text-italic text-grey">
              No options slot
            </q-item-section>
          </q-item>
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = []
</script>
```

### Lazy loading

The following example shows a glimpse of how you can play with lazy loading the options. This means, along with many other things, that `options` prop is not required on first render.

Example "Lazy load options":

```vue
<template>
  <div class="q-gutter-md row items-start">
    <q-select
      filled
      v-model="model"
      use-chips
      label="Lazy load opts"
      :options="options"
      @filter="filterFn"
      @filter-abort="abortFilterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>

    <q-btn
      v-if="options"
      label="Reset"
      color="primary"
      @click="options = null"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(null)

function filterFn(val, update, abort) {
  if (options.value !== null) {
    // already loaded
    update()
    return
  }

  setTimeout(() => {
    update(() => {
      options.value = stringOptions
    })
  }, 2000)
}

function abortFilterFn() {
  console.log('delayed filter aborted')
}
</script>
```

> [!NOTE]
> While options are being loaded, the default loading spinner takes the place of the dropdown icon so the field keeps a constant width. For this reason, when `hide-dropdown-icon` is used the default spinner is not displayed at all (it would make the field's width jump); supply a `loading` slot if you still want an inline indicator in that case.

> [!NOTE]
> When the model already holds a value and `map-options` is used, there is nothing to map it against until the options get loaded, so the field would display the raw value. Starting with Quasar v2.28, QSelect asks for the options on its own in this case: it calls your `@filter` handler once with an empty search string and without opening the menu, so the correct label shows up without any user interaction. The same happens when the model value arrives later (a record loaded from the server, for instance). A value that the loaded options do not contain is not requested again. Should you not want this behavior (when the parent component loads the options itself, for example), opt out with the `no-option-prefetch` prop.

You can dynamically load new options when scroll reaches the end:

Example "Dynamic loading options":

```vue
<template>
  <div style="max-width: 300px">
    <q-select
      filled
      v-model="model"
      multiple
      :options="options"
      :loading="loading"
      @virtual-scroll="onScroll"
    />
  </div>
</template>

<script setup>
import { computed, nextTick, ref } from 'vue'

const allOptions = []
for (let i = 0; i <= 100_000; i++) {
  allOptions.push('Opt ' + i)
}

const pageSize = 50
const lastPage = Math.ceil(allOptions.length / pageSize)

const model = ref(null)
const loading = ref(false)

const nextPage = ref(2)
const options = computed(() =>
  allOptions.slice(0, pageSize * (nextPage.value - 1))
)

function onScroll({ to, ref: compRef }) {
  const lastIndex = options.value.length - 1

  if (loading.value !== true && nextPage.value < lastPage && to === lastIndex) {
    loading.value = true

    setTimeout(() => {
      nextPage.value++
      nextTick(() => {
        compRef.refresh()
        loading.value = false
      })
    }, 500)
  }
}
</script>
```

### Cover mode

Example "Menu covering component":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-select
        filled
        v-model="model"
        :options="options"
        options-cover
        stack-label
        label="Standard"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

### Disable TAB selection

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-select
        disable-tab-selection
        filled
        v-model="model"
        :options="options"
        stack-label
        label="Standard"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

## The display value

By default, the selected value is rendered as a single non-wrapping line, truncated with an ellipsis when there is not enough room for it. Should you need to restyle it (allow it to wrap, for example), target its `q-select__selected-value` CSS class (v2.28+).

Example "Custom display value":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        stack-label
        label="Standard"
        :display-value="`Company: ${model ? model : '*none*'}`"
      >
        <template #append>
          <q-icon
            v-if="model !== null"
            class="cursor-pointer"
            name="clear"
            @click.stop.prevent="model = null"
          />
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref('Twitter')
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

Example "Chips as display value":

```vue
<template>
  <div class="q-gutter-md row items-start">
    <div style="min-width: 250px; max-width: 300px">
      <q-badge color="secondary" class="q-mb-md">
        Model: {{ modelSingle || '*none*' }}
      </q-badge>

      <q-select
        filled
        v-model="modelSingle"
        :options="options"
        use-chips
        stack-label
        label="Single selection"
      />
    </div>

    <div style="min-width: 250px; max-width: 300px">
      <q-badge color="secondary" class="q-mb-md">
        Model: {{ modelMultiple || '[]' }}
      </q-badge>

      <q-select
        filled
        v-model="modelMultiple"
        multiple
        :options="options"
        use-chips
        stack-label
        label="Multiple selection"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const modelSingle = ref('Apple')
const modelMultiple = ref(['Facebook'])
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

The chips also act as a removal affordance: their remove icon takes an option out of the selection and, when the inner input is empty, so does the <kbd>Backspace</kbd> key. If the selection should only be changed through the list of options, use the `no-chip-remove` prop to disable both.

Example "Chips without removal (v2.28+)":

```vue
<template>
  <div style="max-width: 300px">
    <q-select
      filled
      v-model="model"
      multiple
      :options="options"
      use-chips
      no-chip-remove
      stack-label
      label="Non-removable chips"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(['Google', 'Facebook'])
const options = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']
</script>
```

Example "Selected-item slot":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-badge color="secondary" multi-line> Model: "{{ model }}" </q-badge>

      <q-select
        filled
        v-model="model"
        :options="options"
        stack-label
        label="Standard"
      >
        <template #selected>
          Company:
          <q-chip
            v-if="model"
            dense
            square
            color="white"
            text-color="primary"
            class="q-my-none q-ml-xs q-mr-none"
          >
            <q-avatar color="primary" text-color="white" :icon="model.icon" />
            {{ model.label }}
          </q-chip>
          <q-badge v-else>*none*</q-badge>
        </template>
      </q-select>

      <q-select
        filled
        v-model="model"
        :options="options"
        stack-label
        label="Standard"
        color="secondary"
      >
        <template #selected-item="scope">
          <q-chip
            removable
            dense
            @remove="scope.removeAtIndex(scope.index)"
            :tabindex="scope.tabindex"
            color="white"
            text-color="secondary"
            class="q-ma-none"
          >
            <q-avatar
              color="secondary"
              text-color="white"
              :icon="scope.opt.icon"
            />
            {{ scope.opt.label }}
          </q-chip>
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref({
  label: 'Google',
  value: 'goog',
  icon: 'mail'
})

const options = [
  {
    label: 'Google',
    value: 'goog',
    icon: 'mail'
  },
  // ...
  {
    label: 'Oracle',
    value: 'ora',
    disable: true,
    icon: 'casino'
  }
]
</script>
```

## Filtering and autocomplete

### Native attributes with "use-input"

All the attributes set on QSelect that are not in the list of props in the API will be passed to the native input field used (please check `use-input` prop description first to understand what it does) for filtering / autocomplete / adding new value. Some examples: autocomplete, placeholder.

More information: [native input attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).

> [!TIP]
> **Accessibility**
>
> Attributes are applied to the focusable control even without `use-input`. This is particularly useful for `aria-label` or `aria-labelledby`, which set the accessible name that screen readers announce for the select (taking precedence over the name derived from the `label` prop).

Example "Filtering options":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      use-input
      input-debounce="0"
      label="Simple filter"
      :options="options"
      @filter="filterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>

    <q-select
      filled
      v-model="model"
      use-input
      hide-selected
      input-debounce="0"
      label="Hide selected"
      :options="options"
      @filter="filterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update) {
  if (val === '') {
    update(() => {
      options.value = stringOptions

      // here you have access to "ref" which
      // is the Vue reference of the QSelect
    })
    return
  }

  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

Example "Basic filtering":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      :options="options"
      @filter="filterFn"
      hint="Basic filtering"
      style="width: 250px; padding-bottom: 32px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update, abort) {
  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

Example "Filtering on more than 2 chars":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      v-model="model"
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      :options="options"
      @filter="filterFn"
      hint="Minimum 2 characters to trigger filtering"
      style="width: 250px; padding-bottom: 32px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update, abort) {
  if (val.length < 2) {
    abort()
    return
  }

  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().includes(needle))
  })
}
</script>
```

Example "Text autocomplete":

```vue
<template>
  <div class="q-gutter-md row">
    <q-select
      filled
      :model-value="model"
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      :options="options"
      @filter="filterFn"
      @input-value="setModel"
      hint="Text autocomplete"
      style="width: 250px; padding-bottom: 32px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = [
  // ...
].reduce((acc, opt) => {
  for (let i = 1; i <= 5; i++) {
    acc.push(opt + ' ' + i)
  }
  return acc
}, [])

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update, abort) {
  update(() => {
    const needle = val.toLocaleLowerCase()
    options.value = stringOptions.filter(v =>
      v.toLocaleLowerCase().includes(needle)
    )
  })
}

function setModel(val) {
  model.value = val
}
</script>
```

Example "Lazy filtering":

```vue
<template>
  <div class="q-gutter-md">
    <q-select
      filled
      v-model="model"
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      label="Lazy filter"
      :options="options"
      @filter="filterFn"
      @filter-abort="abortFilterFn"
      style="width: 250px"
      hint="With hide-selected and fill-input"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>

    <q-select
      filled
      v-model="model"
      use-input
      use-chips
      input-debounce="0"
      label="Lazy filter"
      :options="options"
      @filter="filterFn"
      @filter-abort="abortFilterFn"
      style="width: 250px"
      hint="With use-chips"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update, abort) {
  // call abort() at any time if you can't retrieve data somehow

  setTimeout(() => {
    update(() => {
      if (val === '') {
        options.value = stringOptions
      } else {
        const needle = val.toLowerCase()
        options.value = stringOptions.filter(v =>
          v.toLowerCase().includes(needle)
        )
      }
    })
  }, 1500)
}

function abortFilterFn() {
  console.log('delayed filter aborted')
}
</script>
```

Example "Selecting option after filtering":

```vue
<template>
  <div class="q-gutter-md">
    <q-select
      filled
      v-model="model"
      clearable
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      label="Focus after filtering"
      :options="options"
      @filter="filterFn"
      @filter-abort="abortFilterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>

    <q-select
      filled
      v-model="model"
      clearable
      use-input
      hide-selected
      fill-input
      input-debounce="0"
      label="Autoselect after filtering"
      :options="options"
      @filter="filterFnAutoselect"
      @filter-abort="abortFilterFn"
      style="width: 250px"
    >
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey"> No results </q-item-section>
        </q-item>
      </template>
    </q-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = [
  // ...
].reduce((acc, opt) => {
  for (let i = 1; i <= 5; i++) {
    acc.push(opt + ' ' + i)
  }
  return acc
}, [])

const model = ref(null)
const options = ref(stringOptions)

function filterFn(val, update, abort) {
  // call abort() at any time if you can't retrieve data somehow

  setTimeout(() => {
    update(
      () => {
        if (val === '') {
          options.value = stringOptions
        } else {
          const needle = val.toLowerCase()
          options.value = stringOptions.filter(v =>
            v.toLowerCase().includes(needle)
          )
        }
      },

      // "compRef" is the Vue reference to the QSelect
      compRef => {
        if (val !== '' && compRef.options.length !== 0) {
          compRef.setOptionIndex(-1) // reset optionIndex in case there is something selected
          compRef.moveOptionSelection(1, true) // focus the first selectable option and do not update the input-value
        }
      }
    )
  }, 300)
}

function filterFnAutoselect(val, update, abort) {
  // call abort() at any time if you can't retrieve data somehow

  setTimeout(() => {
    update(
      () => {
        if (val === '') {
          options.value = stringOptions
        } else {
          const needle = val.toLowerCase()
          options.value = stringOptions.filter(v =>
            v.toLowerCase().includes(needle)
          )
        }
      },

      // "compRef" is the Vue reference to the QSelect
      compRef => {
        if (
          val !== '' &&
          compRef.options.length !== 0 &&
          compRef.getOptionIndex() === -1
        ) {
          compRef.moveOptionSelection(1, true) // focus the first selectable option and do not update the input-value
          compRef.toggleOption(compRef.options[compRef.getOptionIndex()], true) // toggle the focused option
        }
      }
    )
  }, 300)
}

function abortFilterFn() {
  console.log('delayed filter aborted')
}
</script>
```

## Create new values

> [!TIP]
> The following are just a few examples to get you started into making your own QSelect behavior. This is not exhaustive list of possibilities that QSelect offers.
>
> It makes sense to use this feature along with `use-input` prop.

In order to enable the creation of new values, you need to **either specify** the `new-value-mode` prop **and/or** listen for `@new-value` event. If you use both, then the purpose of listening to `@new-value` would be only to override the `new-value-mode` in your custom scenarios.

### The new-value-mode prop

The `new-value-mode` prop value specifies how the value should be added: `add` (adds a value, even if duplicate), `add-unique` (add only if NOT duplicate) or `toggle` (adds value if it's not already in model, otherwise it removes it).

By using this prop you don't need to also listen for `@new-value` event, unless you have some specific scenarios for which you want to override the behavior.

Example "New value mode":

```vue
<template>
  <div class="q-gutter-y-md">
    <q-select
      label="Mode: 'add'"
      filled
      v-model="modelAdd"
      use-input
      use-chips
      multiple
      hide-dropdown-icon
      input-debounce="0"
      new-value-mode="add"
      style="width: 250px"
    />

    <q-select
      label="Mode: 'add-unique'"
      filled
      v-model="modelAddUnique"
      use-input
      use-chips
      multiple
      hide-dropdown-icon
      input-debounce="0"
      new-value-mode="add-unique"
      style="width: 250px"
    />

    <q-select
      label="Mode: 'toggle'"
      filled
      v-model="modelToggle"
      use-input
      use-chips
      multiple
      hide-dropdown-icon
      input-debounce="0"
      new-value-mode="toggle"
      style="width: 250px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const modelAdd = ref(null)
const modelAddUnique = ref(null)
const modelToggle = ref(null)
</script>
```

### The @new-value event

The `@new-value` event is emitted with the value to be added and a `done` callback. The `done` callback has two **optional** parameters:

- the value to be added
- the behavior (same values of `new-value-mode` prop, and when it is specified it overrides that prop -- if it is used) -- default behavior (if not using `new-value-mode`) is to add the value even if it would be a duplicate

Calling `done()` with no parameters simply empties the input box value, without tampering with the model in any way.

Example "Listening on @new-value":

```vue
<template>
  <q-select
    label="Allows duplicates"
    filled
    v-model="model"
    use-input
    use-chips
    multiple
    hide-dropdown-icon
    input-debounce="0"
    @new-value="createValue"
    style="width: 250px"
  />
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)

function createValue(val, done) {
  // specific logic to eventually call done(...) -- or not
  done(val)

  // done callback has two optional parameters:
  //  - the value to be added
  //  - the behavior (same values of new-value-mode prop,
  //    and when it is specified it overrides that prop –
  //    if it is used); default behavior (if not using
  //    new-value-mode) is to add the value even if it would
  //    be a duplicate
}
</script>
```

Example "Adding only unique values":

```vue
<template>
  <q-select
    label="Unique values only"
    filled
    v-model="model"
    use-input
    use-chips
    multiple
    hide-dropdown-icon
    input-debounce="0"
    @new-value="createValue"
    style="width: 250px"
  />
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)

function createValue(val, done) {
  // specific logic to eventually call done(...) -- or not
  done(val, 'add-unique')

  // done callback has two optional parameters:
  //  - the value to be added
  //  - the behavior (same values of new-value-mode prop,
  //    and when it is specified it overrides that prop –
  //    if it is used); default behavior (if not using
  //    new-value-mode) is to add the value even if it would
  //    be a duplicate
}
</script>
```

### Using menu and filtering

Filtering and adding the new values to menu:

Example "Filtering and adding to menu":

```vue
<template>
  <q-select
    filled
    v-model="model"
    use-input
    use-chips
    multiple
    input-debounce="0"
    @new-value="createValue"
    :options="filterOptions"
    @filter="filterFn"
    style="width: 250px"
  />
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const filterOptions = ref(stringOptions)

function createValue(val, done) {
  // Calling done(var) when new-value-mode is not set or "add", or done(var, "add") adds "var" content to the model
  // and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "add-unique", or done(var, "add-unique") adds "var" content to the model
  // only if is not already set
  // and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "toggle", or done(var, "toggle") toggles the model with "var" content
  // (adds to model if not already in the model, removes from model if already has it)
  // and it resets the input textbox to empty string
  // ----
  // If "var" content is undefined/null, then it doesn't tampers with the model
  // and only resets the input textbox to empty string

  if (val.length !== 0) {
    if (!stringOptions.includes(val)) {
      stringOptions.push(val)
    }
    done(val, 'toggle')
  }
}

function filterFn(val, update) {
  update(() => {
    if (val === '') {
      filterOptions.value = stringOptions
    } else {
      const needle = val.toLowerCase()
      filterOptions.value = stringOptions.filter(v =>
        v.toLowerCase().includes(needle)
      )
    }
  })
}
</script>
```

Filters new values (in the example below the value to be added requires at least 3 characters to pass), and does not add to menu:

Example "Filtering without adding to menu":

```vue
<template>
  <q-select
    filled
    v-model="model"
    use-input
    use-chips
    multiple
    input-debounce="0"
    @new-value="createValue"
    :options="filterOptions"
    @filter="filterFn"
    style="width: 250px"
  />
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const filterOptions = ref(stringOptions)

function createValue(val, done) {
  // Calling done(var) when new-value-mode is not set or "add", or done(var, "add") adds "var" content to the model
  // and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "add-unique", or done(var, "add-unique") adds "var" content to the model
  // only if is not already set
  // and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "toggle", or done(var, "toggle") toggles the model with "var" content
  // (adds to model if not already in the model, removes from model if already has it)
  // and it resets the input textbox to empty string
  // ----
  // If "var" content is undefined/null, then it doesn't tampers with the model
  // and only resets the input textbox to empty string

  if (val.length > 2 && !stringOptions.includes(val)) {
    done(val, 'add-unique')
  }
}

function filterFn(val, update) {
  update(() => {
    if (val === '') {
      filterOptions.value = stringOptions
    } else {
      const needle = val.toLowerCase()
      filterOptions.value = stringOptions.filter(v =>
        v.toLowerCase().includes(needle)
      )
    }
  })
}
</script>
```

Generating multiple values from input:

Example "Generating multiple values":

```vue
<template>
  <q-select
    filled
    label="Select multiple values"
    hint="Separate multiple values by [,;|]"
    v-model="model"
    use-input
    use-chips
    multiple
    input-debounce="0"
    @new-value="createValue"
    :options="filterOptions"
    @filter="filterFn"
    style="width: 250px"
  />
</template>

<script setup>
import { ref } from 'vue'

const stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle']

const model = ref(null)
const filterOptions = ref(stringOptions)

function createValue(val, done) {
  // Calling done(var) when new-value-mode is not set or is "add", or done(var, "add") adds "var" content to the model
  // and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "add-unique", or done(var, "add-unique") adds "var" content to the model
  // only if is not already set and it resets the input textbox to empty string
  // ----
  // Calling done(var) when new-value-mode is "toggle", or done(var, "toggle") toggles the model with "var" content
  // (adds to model if not already in the model, removes from model if already has it)
  // and it resets the input textbox to empty string
  // ----
  // If "var" content is undefined/null, then it doesn't tampers with the model
  // and only resets the input textbox to empty string

  if (val.length !== 0) {
    const modelValue = [...(model.value || [])]

    val
      .split(/[,;|]+/)
      .map(v => v.trim())
      .filter(v => v.length !== 0)
      .forEach(v => {
        if (!stringOptions.includes(v)) {
          stringOptions.push(v)
        }
        if (!modelValue.includes(v)) {
          modelValue.push(v)
        }
      })

    done(null)
    model.value = modelValue
  }
}

function filterFn(val, update) {
  update(() => {
    if (val === '') {
      filterOptions.value = stringOptions
    } else {
      const needle = val.toLowerCase()
      filterOptions.value = stringOptions.filter(v =>
        v.toLowerCase().includes(needle)
      )
    }
  })
}
</script>
```

## Sanitization

**By default, all options (included selected ones) are sanitized**. This means that displaying them in HTML format is disabled. However, if you require HTML on your options and you trust their content, then there are a few ways to do this.

You can force the HTML form of the menu options by:

- setting `html` key of the trusted option to `true` (for specific trusted options)
- or by setting `options-html` prop of QSelect (for all options)

The displayed value of QSelect is displayed as HTML if:

- the `display-value-html` prop of QSelect is set
- or you are not using `display-value` and
  - the `options-html` prop of QSelect is set
  - any selected option has `html` key set to `true`

> [!CAUTION]
> If you use `selected` or `selected-item` slots, then you are responsible for sanitization of the display value. The `display-value-html` prop will not apply.

Example "Options in HTML form":

```vue
<template>
  <div style="max-width: 300px">
    <q-badge color="secondary" multi-line class="q-mb-md">
      Model: {{ model || 'empty' }}
    </q-badge>

    <div class="q-gutter-md">
      <q-toggle v-model="optionsHtml" label="Options in HTML form" />

      <q-select
        filled
        v-model="model"
        :options="options"
        label="Standard"
        :options-html="optionsHtml"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const model = ref(null)
const optionsHtml = ref(false)
const options = [
  {
    label: '<span class="text-primary text-bold text-underline">Goo</span>gle',
    value: 'Google'
  },
  {
    label:
      '<span class="text-primary">This is</span> in <span class="text-negative text-bold">HTML form</span> through an option prop',
    value: 'Facebook',
    html: true
  }
]
</script>
```

Example "Display value in HTML form":

```vue
<template>
  <div style="max-width: 300px">
    <q-badge color="secondary" multi-line class="q-mb-md">
      Model: {{ model || 'empty' }}
    </q-badge>

    <div class="q-gutter-md">
      <q-toggle v-model="displayHtml" label="Display value in HTML form" />

      <q-select
        filled
        v-model="model"
        :options="options"
        stack-label
        label="Standard"
        :display-value="`Company: ${model ? model.label : '*none*'}`"
        :display-value-html="displayHtml"
      >
        <template #append>
          <q-icon
            v-if="model !== null"
            class="cursor-pointer"
            name="clear"
            @click.stop.prevent="model = null"
          />
        </template>
      </q-select>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const options = [
  {
    label: '<span class="text-primary">G</span>oogle',
    value: 'Google'
  },
  {
    label: '<span class="text-red">Face</span>book',
    value: 'Facebook'
  }
]

const model = ref(options[0])
const displayHtml = ref(false)
</script>
```

## Render performance

The render performance is NOT affected much by the number of options, unless `map-options` is used on a large set.
Notice the infinite scroll in place which renders additional options as the user scrolls through the list.

> [!TIP]
>
> - (Composition API) To get the best performance while using lots of options, do not wrap the array that you are passing in the `options` prop with ref()/computed()/reactive()/etc. This allows Vue to skip making the list "responsive" to changes.
> - (Options API) To get the best performance while using lots of options, freeze the array that you are passing in the `options` prop using `Object.freeze(items)`. This allows Vue to skip making the list "responsive" to changes.

Example "100k options":

```vue
<template>
  <div style="max-width: 300px">
    <div class="q-gutter-md">
      <q-select filled v-model="model" multiple :options="options" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const options = []
for (let i = 0; i <= 100_000; i++) {
  options.push('Opt ' + i)
}

const model = ref(null)
</script>
```

## Keyboard navigation

When QSelect is focused:

- pressing <kbd>Enter</kbd>, <kbd>Arrow Down</kbd> (or <kbd>Space</kbd> if `use-input` is not set) will open the list of options
- if `use-chips` is set (and `no-chip-remove` is not):
  - pressing <kbd>Shift</kbd> + <kbd>Tab</kbd> will navigate backwards through the QChips (if a QChip is selected <kbd>Tab</kbd> will navigate forward through the QChips)
  - pressing <kbd>Enter</kbd> when a QChip is selected will remove that option from the selection
  - pressing <kbd>Backspace</kbd> will remove the last option from the selection, unless that option is disabled (when `use-input` is set the input should be empty)
- pressing <kbd>Backspace</kbd> when `clearable` is set then:
  - it clears the model (with `null` value) for single selection
  - it removes the last added value for multiple selection
- pressing <kbd>Tab</kbd> (or <kbd>Shift</kbd> + <kbd>Tab</kbd> if `use-chips` is not set or the first QChip is selected) will navigate to the next or previous focusable element on page
- typing text (<kbd>0</kbd> - <kbd>9</kbd> or <kbd>A</kbd> - <kbd>Z</kbd>) if `use-input` is not set will:
  - create a search buffer (will be reset when a new key is not typed for 1.5 seconds) that will be used to search in the options labels
  - select the next option starting with that letter (after the current focused one) if the first key in buffer is typed multiple times
  - select the next option (starting with the current focused one) that matches the typed text (the match is fuzzy - the option label should start with the first letter and contain all the letters)

You can prevent QSelect's action for most keys by preventing its `keydown` event; for example, `@keydown.enter.prevent` keeps <kbd>Enter</kbd> from opening the list of options. The exception is <kbd>Esc</kbd>, whose handling (closing the list of options) is tied to the `keyup` event and cannot be cancelled this way.

When the list of options is opened:

- pressing <kbd>Arrow Up</kbd> or <kbd>Arrow Down</kbd> will navigate up or down in the list of options
- pressing <kbd>Page Up</kbd> or <kbd>Page Down</kbd> will navigate one page up or down in the list of options
- pressing <kbd>Home</kbd> or <kbd>End</kbd> will navigate to the start or end of the list of options (only if you are not using `use-input`, or the input is empty)
- when navigating using arrow keys, navigation will wrap when reaching the start or end of the list
- pressing <kbd>Enter</kbd> (or <kbd>Space</kbd> when `use-input` is not set, or <kbd>Tab</kbd> when `multiple` and `disable-tab-selection` are not set) when an option is selected in the list will:
  - select the option and close the list of options if `multiple` and `disable-tab-selection` are not set
  - toggle the option if `multiple` is set
  - exception: when creating new values (`new-value-mode` prop or `@new-value` event), text you have typed takes precedence over the option that got highlighted automatically for mirroring the current value (upon opening the list or after filtering it); the typed text is then submitted as a new value; an option that you navigated or hovered to still gets selected instead

## Accessibility *(v2.25+)*

QSelect follows the [WAI-ARIA combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). The focus target is always an `<input>` with `role="combobox"` — the real filter input when `use-input` is set, otherwise a readonly input holding the displayed selection, so screen readers read the current value directly off it. It is rendered whatever the field's state, so it always carries the accessible name, the current value and the `id` that the label's `for` points at: a `readonly` QSelect keeps it focusable (showing the field's focused state and emitting `@focus`/`@blur` when reached) and marked `aria-readonly="true"`, while a `disable`d one stays in the accessibility tree — announced as unavailable — but leaves the tab order through the native `disabled` attribute. Neither can open the popup. It carries `aria-expanded` reflecting the popup state, `aria-controls` referencing the list of options (only while the popup with options actually exists, so the reference never points at a missing element), `aria-activedescendant` tracking the highlighted option and `aria-autocomplete` — `list` when `use-input` lets the typed text filter the options, `none` otherwise. Per the pattern, focus never leaves this input while the popup is open — the list is operated from it, with the keys detailed in the [Keyboard navigation](#keyboard-navigation) section above.

The popup content is a `listbox` (always carrying `aria-multiselectable`, `true` or `false` according to the `multiple` prop) whose options carry `aria-selected` plus `aria-setsize` and `aria-posinset`: since the list is virtually scrolled, only a slice of the options exists in the DOM at any time, and these attributes let screen readers still announce each option's true position within the full set.

When the options render in a dialog (see [Options list display mode](#options-list-display-mode)), the control inside the dialog carries this same combobox contract, and the dialog's "Close" button sits in the Tab order right after it, activating with <kbd>Enter</kbd> or <kbd>Space</kbd> and showing the browser's native focus indicator. However the dialog gets dismissed (the Close button, <kbd>Esc</kbd>, tapping the backdrop or selecting an option outside of `multiple` mode), focus returns to the QSelect control, with one deliberate exception: on mobile platforms a `use-input` QSelect restores it only for keyboard-initiated dismissals, so the virtual keyboard that was just put away is not summoned back. Note that iOS lets <kbd>Tab</kbd> reach buttons (this one, or any other on a page) only with the system's Full Keyboard Access setting enabled; <kbd>Esc</kbd> works regardless.

The `label` prop doubles as the combobox's `aria-label`; an `aria-label` or `aria-labelledby` attribute set on QSelect takes precedence, as it is applied to the focusable control (see the Accessibility tip under [Native attributes with "use-input"](#native-attributes-with-use-input)). Two slot-related responsibilities are yours: when using the `option` slot, `v-bind="scope.itemProps"` onto your item, otherwise the option loses its `role="option"`, its id (the `aria-activedescendant` target), `aria-selected` and position attributes; and content placed in the `before-options`/`after-options` slots sits outside the listbox and out of keyboard reach while the popup is open, so avoid interactive elements there. Label association and error announcements are inherited from the field frame — see [QField's Accessibility section](field.md#accessibility).

## Native form submit

When dealing with a native form which has an `action` and a `method` (eg. when using Quasar with ASP.NET controllers), you need to specify the `name` property on QSelect, otherwise formData will not contain it (if it should) - all value are converted to string (native behaviour, so do not use Object values):

Example "Native form":

```vue
<template>
  <q-form @submit="onSubmit" class="q-gutter-md">
    <q-select
      name="preferred_genre"
      v-model="preferred"
      :options="options"
      color="primary"
      filled
      clearable
      label="Preferred genre"
    />

    <q-select
      name="accepted_genres"
      v-model="accepted"
      multiple
      :options="options"
      color="primary"
      filled
      clearable
      label="Accepted genres"
    />

    <div>
      <q-btn label="Submit" type="submit" color="primary" />
    </div>
  </q-form>

  <q-card
    v-if="submitted"
    flat
    bordered
    class="q-mt-md"
    :class="$q.dark.isActive ? 'bg-grey-9' : 'bg-grey-2'"
  >
    <template v-if="submitEmpty">
      <q-card-section>
        Submitted form contains empty formData.
      </q-card-section>
    </template>
    <template v-else>
      <q-card-section
        >Submitted form contains the following formData (key =
        value):</q-card-section
      >
      <q-separator />
      <q-card-section class="row q-gutter-sm items-center">
        <div
          v-for="(item, index) in submitResult"
          :key="index"
          class="q-px-sm q-py-xs bg-grey-8 text-white rounded-borders text-center text-no-wrap"
          >{{ item.name }} = {{ item.value }}</div
        >
      </q-card-section>
    </template>
  </q-card>
</template>

<script setup>
import { ref } from 'vue'

const preferred = ref('rock')
const accepted = ref([])
const options = [
  {
    label: 'Rock',
    value: 'rock'
  },
  {
    label: 'Funk',
    value: 'funk'
  },
  {
    label: 'Pop',
    value: 'pop'
  }
]

const submitted = ref(false)
const submitEmpty = ref(false)
const submitResult = ref([])

function onSubmit(evt) {
  const formData = new FormData(evt.target)
  const data = []

  for (const [name, value] of formData.entries()) {
    data.push({
      name,
      value
    })
  }

  submitted.value = true
  submitResult.value = data
  submitEmpty.value = data.length === 0
}
</script>
```
