svelte-multiselect Svelte MultiSelect

MultiSelect Events

This demo showcases all the events that <MultiSelect> emits. Each emitted event is recorded in the event log panel on the right.

Select options, remove them, use "Remove all", create custom options, navigate with arrow keys, and interact with the input. Try selecting 5 options and adding a 6th to trigger `onmaxreached`, or selecting the same option twice to see `onduplicate`.

Event Log

No events yet. Start clicking around!

<script lang="ts">
  import MultiSelect from '$lib'
  import { ColorSnippet } from '$site'
  import { colors } from '$site/options'

  interface EventLogEntry {
    event: string
    data: string
    timestamp: string
  }

  let events: EventLogEntry[] = $state([])
  let selected_options: string[] = $state([])
  let allowUserOptions = $state(true)

  function log_event(event_name: string, data: unknown): void {
    events = [
      {
        event: event_name,
        data: JSON.stringify(data, null, 2),
        timestamp: new Date().toLocaleTimeString(),
      },
      ...events.slice(0, 9), // Keep last 10 events
    ]
  }
</script>

<div class="demo-grid">
  <section class="demo-section">
    Select options, remove them, use "Remove all", create custom options, navigate with
    arrow keys, and interact with the input. Try selecting 5 options and adding a 6th to
    trigger `onmaxreached`, or selecting the same option twice to see `onduplicate`.

    <label style="display: block; margin-block: 1em">
      <input type="checkbox" bind:checked={allowUserOptions} />
      Allow user options
    </label>

    <MultiSelect
      options={colors}
      placeholder="Select colors or type to create custom..."
      {allowUserOptions}
      createOptionMsg="Create custom color..."
      maxSelect={5}
      onadd={(data) => log_event('onadd', data)}
      onremove={(data) => log_event('onremove', data)}
      onremoveAll={(data) => log_event('onremoveAll', data)}
      onchange={(data) => log_event('onchange', data)}
      oncreate={(data) => log_event('oncreate', data)}
      onopen={(data) => log_event('onopen', data)}
      onclose={(data) => log_event('onclose', data)}
      onsearch={(data) => log_event('onsearch', data)}
      onmaxreached={(data) => log_event('onmaxreached', data)}
      onduplicate={(data) => log_event('onduplicate', data)}
      onactivate={(data) => log_event('onactivate', data)}
      onblur={(event: FocusEvent) =>
      log_event('onblur', {
        type: event.type,
        target: (event.target as HTMLElement)?.tagName,
      })}
      onclick={(event: MouseEvent) =>
      log_event('onclick', {
        type: event.type,
        target: (event.target as HTMLElement)?.tagName,
      })}
      onfocus={(event: FocusEvent) =>
      log_event('onfocus', {
        type: event.type,
        target: (event.target as HTMLElement)?.tagName,
      })}
      onkeydown={(event: KeyboardEvent) =>
      log_event('onkeydown', {
        type: event.type,
        key: event.key,
        code: event.code,
      })}
      bind:selected={selected_options}
    >
      {#snippet children({ idx, option })}
        <ColorSnippet {idx} {option} />
      {/snippet}
    </MultiSelect>
  </section>

  <section class="event-log">
    <header class="log-header">
      <h3 style="margin: 0">Event Log</h3>
      <button onclick={() => events = []}>Clear</button>
    </header>

    {#each events as entry}
      <article class="log-entry">
        <header class="entry-header">
          <span class="event-name">{entry.event}</span>
          <span class="timestamp">{entry.timestamp}</span>
        </header>
        <pre class="log-data">{entry.data}</pre>
      </article>
    {:else}
      <p class="no-events">No events yet. Start clicking around!</p>
    {/each}
  </section>
</div>

Event Reference

Custom Events

EventDescriptionData Structure
onaddFired when an option is added to selection{ option: T }
onremoveFired when an option is removed from selection{ option: T }
onremoveAllFired when all options are removed{ options: T[] }
onchangeFired for any selection change{ option?: T, options?: T[], type: 'add' \| 'remove' \| 'removeAll' }
oncreateFired when user creates a custom option{ option: T }
onopenFired when dropdown opens{ event: Event }
oncloseFired when dropdown closes{ event: Event }
onsearchFired (debounced 150ms) when search text changes{ searchText: string, matchingCount: number }
onmaxreachedFired when user tries to exceed maxSelect{ selected: T[], maxSelect: number, attemptedOption: T }
onduplicateFired when user tries to add duplicate{ option: T }
onactivateFired on keyboard navigation through options{ option: T \| null, index: number \| null }

Native DOM Events

EventDescription
onblurInput loses focus
onclickInput is clicked
onfocusInput gains focus
onkeydownKey is pressed down
onkeyupKey is released
onmousedownMouse button is pressed
onmouseenterMouse enters input area
onmouseleaveMouse leaves input area
ontouchcancelTouch event is cancelled
ontouchendTouch ends
ontouchmoveTouch moves
ontouchstartTouch begins

Event Handling Tips

  1. Destructuring Safety: Check for event.detail before destructuring:

    function handleChange(event) {
      if (!event.detail) return // Guard against undefined detail
      const { option, type } = event.detail
      // ... handle the event
    }
  2. Type Safety: For better type checking in TypeScript projects, use MultiSelectEvents and MultiSelectNativeEvents interfaces:

    <script lang="ts">
     import type { MultiSelectEvents, MultiSelectNativeEvents } from '$lib/types'
    
      const onadd: MultiSelectEvents['onadd'] = (data) => {
        console.log(`onadd`, data)
      }
    
      const onblur: MultiSelectNativeEvents['onblur'] = (event) => {
        console.log(`onblur`, event)
      }
    </script>
    
    <MultiSelect {onadd} {onblur} options={[{ label: `foo` }]} />
  3. Custom Options: The oncreate event only fires when allowUserOptions is enabled and users type text that doesn’t match existing options.

  4. New Events: The onsearch event is debounced (150ms) to avoid excessive callbacks while typing. onactivate only fires during keyboard navigation (arrow keys), not on mouse hover. onduplicate only fires when duplicates={false} (the default).