rob-balfre/svelte-select

Svelte Select. A select component for Svelte

JavaScript

1,352

930 commits

updated Aug 11, 2026

See the code

README

Svelte Select

Svelte Select

A select/autocomplete/typeahead Svelte component.

Demos

💥 Examples of every prop, callback, snippet and more 💥

✨ REPL: Simple ✨

💃 REPL: Show me everything 🕺

Installation

npm install svelte-select

Svelte 5

svelte-select v6+ requires Svelte 5. The component uses runes, callback props instead of createEventDispatcher, and snippet props instead of named slots.

If you are upgrading from an older version, see the migration guide.

Rollup and low/no-build setups

List position and floating is powered by floating-ui, see their package-entry-points docs if you encounter build errors.

Props

PropTypeDefaultDescription
itemsany[][]Array of items available to display / filter
valueanyundefinedSelected value(s). Shape depends on valueMode — see below.
valueModestringitemitem: full item object(s). id: primitive id(s) via itemId. String/primitive items keep primitive value without setting id.
itemIdstringvalueOverride default identifier
labelstringlabelOverride default label
idstringnullid attr for input field
filterTextstring''Text to filter items by
placeholderstringPlease selectPlaceholder text
hideEmptyStatebooleanfalseWhen no items hide list
listOpenbooleanfalseOpen/close list
classstring''container classes
containerStylesstring''Add inline styles to container
clearablebooleantrueEnable clearing of value(s)
disabledbooleanfalseDisable select
multiplebooleanfalseEnable multi-select
searchablebooleantrueIf false search/filtering is disabled
groupHeaderSelectablebooleanfalseEnable selectable group headers
focusedbooleanfalseControls input focus
listAutoWidthbooleantrueIf false will ignore width of select
showChevronbooleanfalseShow chevron
inputAttributesobject{}Pass in HTML attributes to Select's input
placeholderAlwaysShowbooleanfalseWhen multiple placeholder text will always show
loadingbooleanfalseShows loading-icon. loadOptions will override this
listOffsetnumber5px space between select and list
debounceWaitnumber300milliseconds debounce wait
floatingConfigobject{}Floating UI Config
hasErrorbooleanfalseIf true sets error class and styles
namestringnullName attribute of hidden input, helpful for form actions
requiredbooleanfalseIf Select is within a <form> will restrict form submission
multiFullItemClearablebooleanfalseWhen multiple selected items will clear on click
closeListOnChangebooleantrueAfter onchange list will close
clearFilterTextOnBlurbooleantrueIf false, filterText value is preserved on blur

These props support two-way binding: value, filterText, items, loading, listOpen, focused, hoverItemIndex, container, and input.

Snippets

Customize parts of the select by passing snippet props. Kebab-case slot names from v4 map to camelCase snippet names (for example clear-icon → clearIcon, list-prepend → listPrepend). The required slot is now requiredIndicator to avoid clashing with the required prop.

<Select {items}>
  {#snippet prepend()}{/snippet}
  {#snippet selection({ selection, index })}{/snippet} <!-- index only available when multiple -->
  {#snippet clearIcon()}{/snippet}
  {#snippet multiClearIcon()}{/snippet}
  {#snippet loadingIcon()}{/snippet}
  {#snippet chevronIcon({ listOpen })}{/snippet}
  {#snippet listPrepend()}{/snippet}
  {#snippet list({ filteredItems })}{/snippet}
  {#snippet listAppend()}{/snippet}
  {#snippet item({ item, index })}{/snippet}
  {#snippet empty()}{/snippet}
  {#snippet inputHidden({ value })}{/snippet}
  {#snippet requiredIndicator({ value })}{/snippet}
</Select>

Callback props

Pass functions as props to respond to select behaviour. Callbacks receive their payload directly.

CallbackPayloadDescription
onchangevalueFires when the user selects an option
oninputvalueFires when the bound value changes
onselectselectionFires with the selected item when an option is chosen
onfocusFocusEventFires when the select input receives focus
onblurFocusEventFires when the select input loses focus
onclearvalue or removed itemFires when clear is invoked or an item is removed from a multi select
onloaded{ items }Fires when loadOptions resolves
onerror{ type, details }Fires when an error is caught (for example a rejected loadOptions)
onfilterfilteredItemsFires when listOpen: true and items are filtered
onhoverItemhoverItemIndexFires when the hovered list item index changes
<script>
  import Select from 'svelte-select';

  let items = [
    { value: 'one', label: 'One' },
    { value: 'two', label: 'Two' },
  ];

  function handleChange(value) {
    console.log(value);
  }
</script>

<Select {items} onchange={handleChange} />

Items

items can be simple arrays or collections.

<script>
  import Select from 'svelte-select';

  let simple = ['one', 'two', 'three'];

  let collection = [
    { value: 1, label: 'one' },
    { value: 2, label: 'two' },
    { value: 3, label: 'three' },
  ];
</script>

<Select items={simple} />

<Select items={collection} />

They can also be grouped and include non-selectable items.

<script>
  import Select from 'svelte-select';

  const items = [
    {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
    {value: 'pizza', label: 'Pizza', group: 'Savory'},
    {value: 'cake', label: 'Cake', group: 'Sweet', selectable: false},
    {value: 'chips', label: 'Chips', group: 'Savory'},
    {value: 'ice-cream', label: 'Ice Cream', group: 'Sweet'}
  ];

  const groupBy = (item) => item.group;
</script>

<Select {items} {groupBy} />

valueMode

valueMode controls what shape bind:value uses. It must match how you pass value and how your items identify options (itemId, default 'value').

Modevalue (single)value (multiple)On select
item (default)item objectitem object[]list item
idprimitive idprimitive id[]item[itemId] only
  • item — value is the full row. Use with object items. Labels come from value.label (or label prop).
  • id — value is just the identifier. Use for forms/APIs when items are objects. Labels are resolved from items by matching itemId.

String / primitive items: when items is a string (or other primitive) array, value stays a matching primitive automatically — you do not need valueMode="id". On select, value is not upgraded to { value, label }.

For object items, value and valueMode must align — the component does not convert between shapes. Object items with valueMode="id" means value should be 'cake', not { value: 'cake', label: 'Cake' }.

<!-- string items → bind a string (no valueMode needed) -->
<Select items={['one', 'two']} bind:value />

<!-- object items, bind the id only -->
<Select items={collection} valueMode="id" bind:value />

<!-- object items, bind the full item (default) -->
<Select items={collection} bind:value />

You can also use custom collections.

<script>
  import Select from 'svelte-select';

  const itemId = 'id';
  const label = 'title';

  const items = [
    {id: 0, title: 'Foo'},
    {id: 1, title: 'Bar'},
  ];
</script>

<Select {itemId} {label} {items} />

Async Items

To load items asynchronously then loadOptions is the simplest solution. Supply a function that returns a Promise that resolves with a list of items. loadOptions has debounce baked in and fires each time filterText is updated.

<script>
  import Select from 'svelte-select';

  import { someApiCall } from './services';

  async function examplePromise(filterText) {
    // Put your async code here...
    // For example call an API using filterText as your search params
    // When your API responds resolve your Promise
    let res = await someApiCall(filterText);
    return res;
  }
</script>

<Select loadOptions={examplePromise} />

Advanced List Positioning / Floating

svelte-select uses floating-ui to control the list floating. See their docs and pass in your config via the floatingConfig prop.

<script>
  import Select from 'svelte-select';

  let floatingConfig = {
    strategy: 'fixed'
  }
</script>

<Select {floatingConfig} />

Exposed methods and overridable props

These props and methods are exposed for advanced customization. See test/src/tests.js for examples.

Override filtering, grouping, or async loading:

<Select
  itemFilter={(label, filterText) => label.toLowerCase().includes(filterText.toLowerCase())}
  groupBy={(item) => item.group}
  groupFilter={(groups) => groups}
  createGroupHeaderItem={(groupValue) => ({ value: groupValue, label: groupValue })}
  loadOptions={async (filterText) => []}
  debounce={(fn, wait = 1) => setTimeout(fn, wait)}
  filter={customFilter}
  getItems={customGetItems}
/>

Imperative methods (via bind:this):

// Returns the current filtered list items
getFilteredItems();

// Clears the current value and focuses the input
handleClear();

loadOptions must return a Promise that resolves with a list of items. Return { cancelled: true } to keep the loading state active.

Core replaceable helpers live in get-items.js and filter.js.

A11y (Accessibility)

Override these props to change the aria-context and aria-selection text.

<Select
  ariaValues={(values) => `Option ${values}, selected.`}
  ariaListOpen={(label, count) =>
    `You are currently focused on option ${label}. There are ${count} results available.`}
  ariaFocused={() => `Select is focused, type to refine list, press down to open the menu.`}
/>

CSS custom properties (variables)

You can style a component by overriding the available CSS custom properties.

<script>
  import Select from 'svelte-select';
</script>

<Select --border-radius= "10px" --placeholder-color="blue" />

You can also use the inputStyles prop to write in any override styles needed for the input.

<script>
  import Select from 'svelte-select';

  const items = ['One', 'Two', 'Three'];
</script>

<Select {items} inputStyles="box-sizing: border-box;"></Select>

🧪 Experimental: Replace styles (Tailwind, Bootstrap, Bulma etc)

If you'd like to supply your own styles use: import Select from 'svelte-select/no-styles'. Then somewhere in your code or build pipeline add your own. There is a tailwind stylesheet via import 'svelte-select/tailwind.css'. It uses @extend so PostCSS is required.

License

LIL

autocomplete
select
svelte
sveltejs
typeahead

rob-balfre/svelte-select

Svelte Select. A select component for Svelte

JavaScript

1,352

930 commits

updated Aug 11, 2026

See the code

README

Svelte Select

Svelte Select

A select/autocomplete/typeahead Svelte component.

Demos

💥 Examples of every prop, callback, snippet and more 💥

✨ REPL: Simple ✨

💃 REPL: Show me everything 🕺

Installation

npm install svelte-select

Svelte 5

svelte-select v6+ requires Svelte 5. The component uses runes, callback props instead of createEventDispatcher, and snippet props instead of named slots.

If you are upgrading from an older version, see the migration guide.

Rollup and low/no-build setups

List position and floating is powered by floating-ui, see their package-entry-points docs if you encounter build errors.

Props

PropTypeDefaultDescription
itemsany[][]Array of items available to display / filter
valueanyundefinedSelected value(s). Shape depends on valueMode — see below.
valueModestringitemitem: full item object(s). id: primitive id(s) via itemId. String/primitive items keep primitive value without setting id.
itemIdstringvalueOverride default identifier
labelstringlabelOverride default label
idstringnullid attr for input field
filterTextstring''Text to filter items by
placeholderstringPlease selectPlaceholder text
hideEmptyStatebooleanfalseWhen no items hide list
listOpenbooleanfalseOpen/close list
classstring''container classes
containerStylesstring''Add inline styles to container
clearablebooleantrueEnable clearing of value(s)
disabledbooleanfalseDisable select
multiplebooleanfalseEnable multi-select
searchablebooleantrueIf false search/filtering is disabled
groupHeaderSelectablebooleanfalseEnable selectable group headers
focusedbooleanfalseControls input focus
listAutoWidthbooleantrueIf false will ignore width of select
showChevronbooleanfalseShow chevron
inputAttributesobject{}Pass in HTML attributes to Select's input
placeholderAlwaysShowbooleanfalseWhen multiple placeholder text will always show
loadingbooleanfalseShows loading-icon. loadOptions will override this
listOffsetnumber5px space between select and list
debounceWaitnumber300milliseconds debounce wait
floatingConfigobject{}Floating UI Config
hasErrorbooleanfalseIf true sets error class and styles
namestringnullName attribute of hidden input, helpful for form actions
requiredbooleanfalseIf Select is within a <form> will restrict form submission
multiFullItemClearablebooleanfalseWhen multiple selected items will clear on click
closeListOnChangebooleantrueAfter onchange list will close
clearFilterTextOnBlurbooleantrueIf false, filterText value is preserved on blur

These props support two-way binding: value, filterText, items, loading, listOpen, focused, hoverItemIndex, container, and input.

Snippets

Customize parts of the select by passing snippet props. Kebab-case slot names from v4 map to camelCase snippet names (for example clear-icon → clearIcon, list-prepend → listPrepend). The required slot is now requiredIndicator to avoid clashing with the required prop.

<Select {items}>
  {#snippet prepend()}{/snippet}
  {#snippet selection({ selection, index })}{/snippet} <!-- index only available when multiple -->
  {#snippet clearIcon()}{/snippet}
  {#snippet multiClearIcon()}{/snippet}
  {#snippet loadingIcon()}{/snippet}
  {#snippet chevronIcon({ listOpen })}{/snippet}
  {#snippet listPrepend()}{/snippet}
  {#snippet list({ filteredItems })}{/snippet}
  {#snippet listAppend()}{/snippet}
  {#snippet item({ item, index })}{/snippet}
  {#snippet empty()}{/snippet}
  {#snippet inputHidden({ value })}{/snippet}
  {#snippet requiredIndicator({ value })}{/snippet}
</Select>

Callback props

Pass functions as props to respond to select behaviour. Callbacks receive their payload directly.

CallbackPayloadDescription
onchangevalueFires when the user selects an option
oninputvalueFires when the bound value changes
onselectselectionFires with the selected item when an option is chosen
onfocusFocusEventFires when the select input receives focus
onblurFocusEventFires when the select input loses focus
onclearvalue or removed itemFires when clear is invoked or an item is removed from a multi select
onloaded{ items }Fires when loadOptions resolves
onerror{ type, details }Fires when an error is caught (for example a rejected loadOptions)
onfilterfilteredItemsFires when listOpen: true and items are filtered
onhoverItemhoverItemIndexFires when the hovered list item index changes
<script>
  import Select from 'svelte-select';

  let items = [
    { value: 'one', label: 'One' },
    { value: 'two', label: 'Two' },
  ];

  function handleChange(value) {
    console.log(value);
  }
</script>

<Select {items} onchange={handleChange} />

Items

items can be simple arrays or collections.

<script>
  import Select from 'svelte-select';

  let simple = ['one', 'two', 'three'];

  let collection = [
    { value: 1, label: 'one' },
    { value: 2, label: 'two' },
    { value: 3, label: 'three' },
  ];
</script>

<Select items={simple} />

<Select items={collection} />

They can also be grouped and include non-selectable items.

<script>
  import Select from 'svelte-select';

  const items = [
    {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
    {value: 'pizza', label: 'Pizza', group: 'Savory'},
    {value: 'cake', label: 'Cake', group: 'Sweet', selectable: false},
    {value: 'chips', label: 'Chips', group: 'Savory'},
    {value: 'ice-cream', label: 'Ice Cream', group: 'Sweet'}
  ];

  const groupBy = (item) => item.group;
</script>

<Select {items} {groupBy} />

valueMode

valueMode controls what shape bind:value uses. It must match how you pass value and how your items identify options (itemId, default 'value').

Modevalue (single)value (multiple)On select
item (default)item objectitem object[]list item
idprimitive idprimitive id[]item[itemId] only
  • item — value is the full row. Use with object items. Labels come from value.label (or label prop).
  • id — value is just the identifier. Use for forms/APIs when items are objects. Labels are resolved from items by matching itemId.

String / primitive items: when items is a string (or other primitive) array, value stays a matching primitive automatically — you do not need valueMode="id". On select, value is not upgraded to { value, label }.

For object items, value and valueMode must align — the component does not convert between shapes. Object items with valueMode="id" means value should be 'cake', not { value: 'cake', label: 'Cake' }.

<!-- string items → bind a string (no valueMode needed) -->
<Select items={['one', 'two']} bind:value />

<!-- object items, bind the id only -->
<Select items={collection} valueMode="id" bind:value />

<!-- object items, bind the full item (default) -->
<Select items={collection} bind:value />

You can also use custom collections.

<script>
  import Select from 'svelte-select';

  const itemId = 'id';
  const label = 'title';

  const items = [
    {id: 0, title: 'Foo'},
    {id: 1, title: 'Bar'},
  ];
</script>

<Select {itemId} {label} {items} />

Async Items

To load items asynchronously then loadOptions is the simplest solution. Supply a function that returns a Promise that resolves with a list of items. loadOptions has debounce baked in and fires each time filterText is updated.

<script>
  import Select from 'svelte-select';

  import { someApiCall } from './services';

  async function examplePromise(filterText) {
    // Put your async code here...
    // For example call an API using filterText as your search params
    // When your API responds resolve your Promise
    let res = await someApiCall(filterText);
    return res;
  }
</script>

<Select loadOptions={examplePromise} />

Advanced List Positioning / Floating

svelte-select uses floating-ui to control the list floating. See their docs and pass in your config via the floatingConfig prop.

<script>
  import Select from 'svelte-select';

  let floatingConfig = {
    strategy: 'fixed'
  }
</script>

<Select {floatingConfig} />

Exposed methods and overridable props

These props and methods are exposed for advanced customization. See test/src/tests.js for examples.

Override filtering, grouping, or async loading:

<Select
  itemFilter={(label, filterText) => label.toLowerCase().includes(filterText.toLowerCase())}
  groupBy={(item) => item.group}
  groupFilter={(groups) => groups}
  createGroupHeaderItem={(groupValue) => ({ value: groupValue, label: groupValue })}
  loadOptions={async (filterText) => []}
  debounce={(fn, wait = 1) => setTimeout(fn, wait)}
  filter={customFilter}
  getItems={customGetItems}
/>

Imperative methods (via bind:this):

// Returns the current filtered list items
getFilteredItems();

// Clears the current value and focuses the input
handleClear();

loadOptions must return a Promise that resolves with a list of items. Return { cancelled: true } to keep the loading state active.

Core replaceable helpers live in get-items.js and filter.js.

A11y (Accessibility)

Override these props to change the aria-context and aria-selection text.

<Select
  ariaValues={(values) => `Option ${values}, selected.`}
  ariaListOpen={(label, count) =>
    `You are currently focused on option ${label}. There are ${count} results available.`}
  ariaFocused={() => `Select is focused, type to refine list, press down to open the menu.`}
/>

CSS custom properties (variables)

You can style a component by overriding the available CSS custom properties.

<script>
  import Select from 'svelte-select';
</script>

<Select --border-radius= "10px" --placeholder-color="blue" />

You can also use the inputStyles prop to write in any override styles needed for the input.

<script>
  import Select from 'svelte-select';

  const items = ['One', 'Two', 'Three'];
</script>

<Select {items} inputStyles="box-sizing: border-box;"></Select>

🧪 Experimental: Replace styles (Tailwind, Bootstrap, Bulma etc)

If you'd like to supply your own styles use: import Select from 'svelte-select/no-styles'. Then somewhere in your code or build pipeline add your own. There is a tailwind stylesheet via import 'svelte-select/tailwind.css'. It uses @extend so PostCSS is required.

License

LIL

autocomplete
select
svelte
sveltejs
typeahead

Project health

Overall

4.8/10

Maintained

4/10

Code review

1/10
OpenSSF Scorecard

Latest release

6.0.0 · Aug 11, 2026

Releases this year

1

Significant stargazers

axel7083

40 followers · starred Jan 2025

Mihir Chaturvedi

417 followers · starred Jul 2021

Ilija Tovilo

483 followers · starred Sep 2021

Long Nguyen

42 followers · starred Dec 2022