Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/janosh/svelte-multiselect
Keyboard-friendly, accessible and highly customizable multi-select component
https://github.com/janosh/svelte-multiselect
forms input multi-select single-select svelte svelte-component
Last synced: 6 days ago
JSON representation
Keyboard-friendly, accessible and highly customizable multi-select component
- Host: GitHub
- URL: https://github.com/janosh/svelte-multiselect
- Owner: janosh
- License: mit
- Created: 2021-05-07T12:37:38.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-10-24T09:03:40.000Z (2 months ago)
- Last Synced: 2024-10-25T05:44:14.791Z (2 months ago)
- Topics: forms, input, multi-select, single-select, svelte, svelte-component
- Language: TypeScript
- Homepage: https://multiselect.janosh.dev
- Size: 1.08 MB
- Stars: 283
- Watchers: 5
- Forks: 36
- Open Issues: 15
-
Metadata Files:
- Readme: readme.md
- Changelog: changelog.md
- Contributing: contributing.md
- License: license
Awesome Lists containing this project
- awesome-sveltekit - [code
README
βSvelte MultiSelect
[![Tests](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml/badge.svg)](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
[![GitHub Pages](https://github.com/janosh/svelte-multiselect/actions/workflows/gh-pages.yml/badge.svg)](https://github.com/janosh/svelte-multiselect/actions/workflows/gh-pages.yml)
[![NPM version](https://img.shields.io/npm/v/svelte-multiselect?logo=NPM&color=purple)](https://npmjs.com/package/svelte-multiselect)
[![Needs Svelte version](https://img.shields.io/npm/dependency-version/svelte-multiselect/svelte?color=teal&logo=Svelte&label=Svelte)](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md)
[![REPL](https://img.shields.io/badge/Svelte-REPL-blue?label=Try%20it!)](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
[![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-darkblue?logo=stackblitz)](https://stackblitz.com/github/janosh/svelte-multiselect)
Keyboard-friendly, accessible and highly customizable multi-select component.
## π‘ β Features
- **Bindable:** `bind:selected` gives you an array of the currently selected options. Thanks to Svelte's 2-way binding, it can also control the component state externally through assignment `selected = ['foo', 42]`.
- **Keyboard friendly** for mouse-less form completion
- **No run-time deps:** needs only Svelte as dev dependency
- **Dropdowns:** scrollable lists for large numbers of options
- **Searchable:** start typing to filter options
- **Tagging:** selected options are listed as tags within the input
- **Single / multiple select:** pass `maxSelect={1, 2, 3, ...}` prop to restrict the number of selectable options
- **Configurable:** see props## π§ͺ β Coverage
| Statements | Branches | Lines |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| ![Statements](https://img.shields.io/badge/statements-97.94%25-brightgreen.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-79.39%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-97.94%25-brightgreen.svg?style=flat) |## π β Breaking changes
- **8.0.0** (2022-10-22)Β
- Props `selectedLabels` and `selectedValues` were removed. If you were using them, they were equivalent to assigning `bind:selected` to a local variable and then running `selectedLabels = selected.map(option => option.label)` and `selectedValues = selected.map(option => option.value)` if your options were objects with `label` and `value` keys. If they were simple strings/numbers, there was no point in using `selected{Labels,Values}` anyway. [PR 138](https://github.com/janosh/svelte-multiselect/pull/138)
- Prop `noOptionsMsg` was renamed to `noMatchingOptionsMsg`. [PR 133](https://github.com/janosh/svelte-multiselect/pull/133).
- **v8.3.0** (2023-01-25)Β `addOptionMsg` was renamed to `createOptionMsg` (no major since version since it's rarely used) [sha](https://github.com/janosh/svelte-multiselect/commits).
- **v9.0.0** (2023-06-01)Β Svelte bumped from v3 to v4. Also, not breaking but noteworthy: MultiSelect received a default slot that functions as both `"option"` and `"selected"`. If you previously had two identical slots for `"option"` and `"selected"`, you can now remove the `name` from one of them and drop the other:```diff
-
-
+
```- **v10.0.0** (2023-06-23)Β `duplicateFunc()` renamed to `key` in [#238](https://github.com/janosh/svelte-multiselect/pull/238). Signature changed:
```diff
- duplicateFunc: (op1: T, op2: T) => boolean = (op1, op2) => `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
+ key: (opt: T) => unknown = (opt) => `${get_label(opt)}`.toLowerCase()
```Rather than implementing custom equality in `duplicateFunc`, the `key` function is now expected to map options to a unique identifier. `key(op1) === key(op2)` should mean `op1` and `op2` are the same option. `key` can return any type but usually best to return primitives (`string`, `number`, ...) for Svelte keyed each blocks (see [#217](https://github.com/janosh/svelte-multiselect/pull/217)).
## π¨ β Installation
```sh
npm install --dev svelte-multiselect
pnpm add -D svelte-multiselect
yarn add --dev svelte-multiselect
```## π β Usage
```svelte
import MultiSelect from 'svelte-multiselect'
const ui_libs = [`Svelte`, `React`, `Vue`, `Angular`, `...`]
let selected = []
Favorite Frontend Tools?
selected = {JSON.stringify(selected)}
```
## π£ β Props
Full list of props/bindable variables for this component. The `Option` type you see below is defined in [`src/lib/index.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/index.ts) and can be imported as `import { type Option } from 'svelte-multiselect'`.
1. ```ts
activeIndex: number | null = null
```Zero-based index of currently active option in the array of currently matching options, i.e. if the user typed a search string into the input and only a subset of options match, this index refers to the array position of the matching subset of options
1. ```ts
activeOption: Option | null = null
```Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
1. ```ts
createOptionMsg: string | null = `Create this option...`
```The message shown to users when `allowUserOptions` is truthy and they entered text that doesn't match any existing options to suggest creating a new option from the entered text. Emits `console.error` if `allowUserOptions` is `true` or `'append'` and `createOptionMsg=''` to since users might be unaware they can create new option. The error can be silenced by setting `createOptionMsg=null` indicating developer intent is to e.g. use MultiSelect as a tagging component where a user message might be unwanted.
1. ```ts
allowEmpty: boolean = false
```Whether to `console.error` if dropdown list of options is empty. `allowEmpty={false}` will suppress errors. `allowEmpty={true}` will report a console error if component is not `disabled`, not in `loading` state and doesn't `allowUserOptions`.
1. ```ts
allowUserOptions: boolean | 'append' = false
```Whether users can enter values that are not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected.
If `allowUserOptions` is `true` or `'append'` then the type `object | number | string` of entered value is determined by `typeof options[0]` (i.e. the first option in the dropdown list) to keep type homogeneity.1. ```ts
autocomplete: string = `off`
```Applied to the ``. Specifies if browser is permitted to auto-fill this form field. Should usually be one of `'on'` or `'off'` but see [MDN docs](https://developer.mozilla.org/docs/Web/HTML/Attributes/autocomplete) for other admissible values.
1. ```ts
autoScroll: boolean = true
````false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys.
1. ```ts
breakpoint: number = 800
```Screens wider than `breakpoint` in pixels will be considered `'desktop'`, everything narrower as `'mobile'`.
1. ```ts
defaultDisabledTitle: string = `This option is disabled`
```Title text to display when user hovers over a disabled option. Each option can override this through its `disabledTitle` attribute.
1. ```ts
disabled: boolean = false
```Disable the component. It will still be rendered but users won't be able to interact with it.
1. ```ts
disabledInputTitle: string = `This input is disabled`
```Tooltip text to display on hover when the component is in `disabled` state.
1. ```ts
duplicates: boolean = false
```Whether to allow users to select duplicate options. Applies only to the selected item list, not the options dropdown. Keeping that free of duplicates is left to developer. The selected item list can have duplicates if `allowUserOptions` is truthy, `duplicates` is `true` and users create the same option multiple times. Use `duplicateOptionMsg` to customize the message shown to user if `duplicates` is `false` and users attempt this and `key` to customize when a pair of options is considered equal.
1. ```ts
duplicateOptionMsg: string = `This option is already selected`
```Text to display to users when `allowUserOptions` is truthy and they try to create a new option that's already selected.
1. ```ts
key: (opt: T) => unknown = (opt) => `${get_label(opt)}`.toLowerCase()
```A function that maps options to a value by which equality of options is determined. Defaults to mapping options to their lower-cased label. E.g. by default ``const opt1 = { label: `foo`, id: 1 }`` and ``const opt2 = { label: `foo`, id: 2 }`` are considered equal. If you want to consider them different, you can set `key` to e.g. `key={(opt) => opt.id}` or ``key={(opt) => `${opt.label}-${opt.id}}`` or even `key={JSON.stringify}`.
1. ```ts
filterFunc = (opt: Option, searchText: string): boolean => {
if (!searchText) return true
return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase())
}
```Customize how dropdown options are filtered when user enters search string into ``. Defaults to:
1. ```ts
closeDropdownOnSelect: boolean | 'desktop' = `desktop`
```One of `true`, `false` or `'desktop'`. Whether to close the dropdown list after selecting a dropdown item. If `true`, component will loose focus and `dropdown` is closed. `'desktop'` means `false` if current window width is larger than the current value of `breakpoint` prop (default is 800, meaning screen width in pixels). This is to align with the default behavior of many mobile browsers like Safari which close dropdowns after selecting an option while desktop browsers facilitate multi-selection by leaving dropdowns open.
1. ```ts
form_input: HTMLInputElement | null = null
```Handle to the `` DOM node that's responsible for form validity checks and passing selected options to form submission handlers. Only available after component mounts (`null` before then).
1. ```ts
highlightMatches: boolean = true
```Whether to highlight text in the dropdown options that matches the current user-entered search query. Uses the [CSS Custom Highlight API](https://developer.mozilla.org/docs/Web/API/CSS_Custom_Highlight_API) with [limited browser support](https://caniuse.com/mdn-api_css_highlights) (70% as of May 2023) and [styling options](https://developer.mozilla.org/docs/Web/CSS/::highlight). See `::highlight(sms-search-matches)` below for available CSS variables.
1. ```ts
id: string | null = null
```Applied to the `` element for associating HTML form ``s with this component for accessibility. Also, clicking a `` with same `for` attribute as `id` will focus this component.
1. ```ts
input: HTMLInputElement | null = null
```Handle to the `` DOM node. Only available after component mounts (`null` before then).
1. ```ts
inputmode: string | null = null
```The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow mobile browsers to display an appropriate virtual on-screen keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details. If you want to suppress the on-screen keyboard to leave full-screen real estate for the dropdown list of options, set `inputmode="none"`.
1. ```ts
inputStyle: string | null = null
```One-off CSS rules applied to the `` element.
1. ```ts
invalid: boolean = false
```If `required = true, 1, 2, ...` and user tries to submit form but `selected = []` is empty/`selected.length < required`, `invalid` is automatically set to `true` and CSS class `invalid` applied to the top-level `div.multiselect`. `invalid` class is removed as soon as any change to `selected` is registered. `invalid` can also be controlled externally by binding to it `` and setting it to `true` based on outside events or custom validation.
1. ```ts
liOptionStyle: string | null = null
```One-off CSS rules applied to the `
1. ```ts
liSelectedStyle: string | null = null
```
One-off CSS rules applied to the `
1. ```ts
loading: boolean = false
```
Whether the component should display a spinner to indicate it's in loading state. Use `` to specify a custom spinner.
1. ```ts
matchingOptions: Option[] = []
```
List of options currently displayed to the user. Same as `options` unless the user entered `searchText` in which case this array contains only those options for which `filterFunc = (op: Option, searchText: string) => boolean` returned `true`.
1. ```ts
maxOptions: number | undefined = undefined
```
Positive integer to limit the number of options displayed in the dropdown. `undefined` and 0 mean no limit.
1. ```ts
maxSelect: number | null = null
```
Positive integer to limit the number of options users can pick. `null` means no limit. `maxSelect={1}` will change the type of `selected` to be a single `Option` (or `null`) (not a length-1 array). Likewise, the type of `selectedLabels` changes from `(string | number)[]` to `string | number | null` and `selectedValues` from `unknown[]` to `unknown | null`. `maxSelect={1}` will also give `div.multiselect` a class of `single`. I.e. you can target the selector `div.multiselect.single` to give single selects a different appearance from multi selects.
1. ```ts
maxSelectMsg: ((current: number, max: number) => string) | null = (
current: number,
max: number
) => (max > 1 ? `${current}/${max}` : ``)
```
Inform users how many of the maximum allowed options they have already selected. Set `maxSelectMsg={null}` to not show a message. Defaults to `null` when `maxSelect={1}` or `maxSelect={null}`. Else if `maxSelect > 1`, defaults to:
```ts
maxSelectMsg = (current: number, max: number) => `${current}/${max}`
```
Use CSS selector `span.max-select-msg` (or prop `maxSelectMsgClass` if you're using a CSS framework like Tailwind) to customize appearance of the message container.
1. ```ts
minSelect: number | null = null
```
Conditionally render the `x` button which removes a selected option depending on the number of selected options. Meaning all remove buttons disappear if `selected.length <= minSelect`. E.g. if 2 options are selected and `minSelect={3}`, users will not be able to remove any selections until they selected more than 3 options.
Note: Prop `required={3}` should be used instead if you only care about the component state at form submission time. `minSelect={3}` should be used if you want to place constraints on component state at all times.
1. ```ts
name: string | null = null
```
Applied to the `` element. Sets the key of this field in a submitted form data object. See [form example](https://janosh.github.io/svelte-multiselect/form).
1. ```ts
noMatchingOptionsMsg: string = `No matching options`
```
What message to show if no options match the user-entered search string.
1. ```ts
open: boolean = false
```
Whether the dropdown list is currently visible. Is two-way bindable, i.e. can be used for external control of when the options are visible.
1. ```ts
options: Option[]
```
**The only required prop** (no default). Array of strings/numbers or `Option` objects to be listed in the dropdown. The only required key on objects is `label` which must also be unique. An object's `value` defaults to `label` if `undefined`. You can add arbitrary additional keys to your option objects. A few keys like `preselected` and `title` have special meaning though. See type `ObjectOption` in [`/src/lib/types.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/types.ts) for all special keys and their purpose.
1. ```ts
outerDiv: HTMLDivElement | null = null
```
Handle to outer `
1. ```ts
parseLabelsAsHtml: boolean = false
```
Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting).
1. ```ts
pattern: string | null = null
```
The pattern attribute specifies a regular expression which the input's value must match. If a non-null value doesn't match the `pattern` regex, the read-only `patternMismatch` property will be `true`. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Attributes/pattern) for details.
1. ```ts
placeholder: string | null = null
```
String shown in the text input when no option is selected.
1. ```ts
removeAllTitle: string = `Remove all`
```
Title text to display when user hovers over remove-all button.
1. ```ts
removeBtnTitle: string = `Remove`
```
Title text to display when user hovers over button to remove selected option (which defaults to a cross icon).
1. ```ts
required: boolean | number = false
```
If `required = true, 1, 2, ...` forms can't be submitted without selecting given number of options. `true` means 1. `false` means even empty MultiSelect will pass form validity check. If user tries to submit a form containing MultiSelect with less than the required number of options, submission is aborted, MultiSelect scrolls into view and shows message "Please select at least `required` options".
1. ```ts
resetFilterOnAdd: boolean = true
```
Whether text entered into the input to filter options in the dropdown list is reset to empty string when user selects an option.
1. ```ts
searchText: string = ``
```
Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text.
1. ```ts
selected: Option[] =
options
?.filter((op) => (op as ObjectOption)?.preselected)
.slice(0, maxSelect ?? undefined) ?? []
```
Array of currently selected options. Supports 2-way binding `bind:selected={[1, 2, 3]}` to control component state externally. Can be passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction.
1. ```ts
sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
```
Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://janosh.github.io/svelte-multiselect/sort-selected) example.
1. ```ts
selectedOptionsDraggable: boolean = !sortSelected
```
Whether selected options are draggable so users can change their order.
1. ```ts
style: string | null = null
```
One-off CSS rules applied to the outer `
1. ```ts
ulSelectedStyle: string | null = null
```
One-off CSS rules applied to the `
- ` that wraps the list of selected options.
1. ```ts
ulOptionsStyle: string | null = null
```
One-off CSS rules applied to the `