svelte-multiselect Svelte MultiSelect

« home

Attachments

Exported from svelte-multiselect/attachments:

tooltip

<script>
  import { Icon, tooltip } from '$lib'

  let custom_delay = $state(0)
</script>

<div style="display: flex; gap: 3em">
  <button
    aria-label="More info"
    style="padding: 0.4em 0.8em"
    {@attach tooltip({
      content: `<strong>Custom</strong> <em>HTML</em> tooltip`,
      placement: `right`,
      delay: custom_delay,
    })}
  >
    Hover me
  </button>

  <label style="display: inline-flex; gap: 0.5em; align-items: center">
    Delay (ms)
    <input type="number" min="0" step="50" bind:value={custom_delay} style="width: 6em" />
  </label>
</div>

<!-- Placement showcase -->
<div style="display: flex; gap: 1em; margin: 2em 0">
  <button {@attach tooltip({ content: `Top`, placement: `top` })}>
    Top
  </button>
  <button {@attach tooltip({ content: `Right`, placement: `right` })}>
    Right
  </button>
  <button {@attach tooltip({ content: `Bottom (default)`, placement: `bottom` })}>
    Bottom
  </button>
  <button {@attach tooltip({ content: `Left`, placement: `left` })}>
    Left
  </button>
</div>

<!-- Style variations via CSS variables to demonstrate customization -->
<div
  style="display: flex; gap: 1em; margin: 2em 0"
>
  <button
    style="--tooltip-bg: white; --text-color: #111; --tooltip-border: 1px solid rgba(0, 0, 0, 0.18); --tooltip-font-size: 12px; --tooltip-arrow-size: 5; --tooltip-opacity: 0.95"
    {@attach tooltip({ content: `Light tooltip`, placement: `top` })}
  >
    Light style
  </button>
  <button
    style="--tooltip-bg: #0f2a43; --text-color: #d7ecff; --tooltip-border: 1px solid rgba(0, 128, 255, 0.4); --tooltip-shadow: drop-shadow(0 4px 12px rgba(0, 128, 255, 0.25)); --tooltip-font-size: 14px; --tooltip-arrow-size: 8; --tooltip-opacity: 1"
    {@attach tooltip({ content: `Info tooltip`, placement: `right` })}
  >
    Info style
  </button>
  <button
    style="--tooltip-bg: rgba(255, 50, 50, 0.9); --text-color: white; --tooltip-border: 1px solid rgba(255, 50, 50, 0.9); --tooltip-radius: 3px; --tooltip-font-size: 12px; --tooltip-arrow-size: 10; --tooltip-opacity: 0.9"
    {@attach tooltip({ content: `Warning tooltip`, placement: `bottom` })}
  >
    Warning tooltip
  </button>
  <button
    style="--tooltip-bg: white; --text-color: #111; --tooltip-border: 1px solid rgba(255, 255, 255, 0.15); --tooltip-font-size: 16px; --tooltip-arrow-size: 12; --tooltip-padding: 10px 12px"
    {@attach tooltip({ content: `Large text + big arrow`, placement: `left` })}
  >
    Large text
  </button>
</div>

<!-- Note: Do not pass untrusted HTML/strings to `content` or `style`. Sanitize or use plain text. -->
<div style="display: flex; gap: 1em; margin: 2em 0">
  <button
    style="--tooltip-bg: #2d3748; --text-color: #e2e8f0; --tooltip-border: 2px solid #4299e1; --tooltip-arrow-size: 8"
    {@attach tooltip({
      content: `Custom style + border arrow`,
      placement: `top`,
      style:
        `box-shadow: 0 10px 25px rgba(66, 153, 225, 0.3); transform: scale(1.05);`,
    })}
  >
    Custom style
  </button>
  <button
    style="--tooltip-bg: #f56565; --text-color: white; --tooltip-border: 1px solid #c53030"
    {@attach tooltip({ content: `Disabled tooltip`, disabled: true })}
  >
    Disabled (no tooltip)
  </button>
  <button
    style="--tooltip-bg: #48bb78; --text-color: white; --tooltip-border: 3px solid #38a169; --tooltip-arrow-size: 12"
    {@attach tooltip({
      content: `Thick border with matching arrow`,
      placement: `bottom`,
      style: `font-weight: bold; letter-spacing: 0.5px;`,
    })}
  >
    Thick border
  </button>
</div>
<!-- Attach once to a container: children with title/aria-label/data-title get their own tooltip -->
<div style="display: flex; gap: 1em; margin: 2em 0" {@attach tooltip()}>
  <button title="Added via title attribute">Title-based</button>
  <button aria-label="Added via aria-label">aria-label</button>
  <button data-title="Added via data-title">data-title</button>
</div>

<!-- Text wrapping and shrink-to-fit width demo -->
<div style="display: flex; gap: 1em; margin: 2em 0; flex-wrap: wrap">
  <button
    style="--tooltip-max-width: 200px"
    {@attach tooltip({
      content: `This tooltip uses balanced text wrapping for even line lengths`,
      placement: `top`,
    })}
  >
    Balanced wrapping
  </button>
  <button
    style="--tooltip-max-width: 220px"
    {@attach tooltip({
      content:
        `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`,
      placement: `bottom`,
    })}
  >
    Long text shrink-to-fit
  </button>
</div>

Reactive Tooltip Content

Tooltip content updates reactively via MutationObserver when title, aria-label, or data-title changes:

<script>
  import { tooltip } from '$lib'
  let text = $state(`Edit me!`)
</script>

<input bind:value={text} style="width: 16ch" />
<button title={text} {@attach tooltip({ placement: `right` })}>Hover me</button>

draggable

Drag me this text is also draggable
Drag with custom callbacks
this text is not draggable

last pointer: —

<script>
  import { draggable } from '$lib'

  let last_drag = $state('')
</script>

<div class="drag-area">
  <!-- Absolute positioned box → default handle is the node itself -->
  <div
    class="drag-box"
    style="position: absolute; left: 1rem; top: 1rem"
    {@attach draggable({
      on_drag: (event) => last_drag = `${event.clientX}, ${event.clientY}`,
    })}
  >
    Drag me
    <small style="display: block; opacity: 0.7">this text is also draggable</small>
  </div>

  <!-- Second draggable with custom handle and callbacks -->
  <div
    class="drag-box"
    style="position: absolute; left: 12rem; top: 8rem; width: 14rem"
    {@attach draggable({
      handle_selector: `.drag-handle`,
      on_drag_start: () => last_drag = `start`,
      on_drag: (event) => last_drag = `${event.clientX}, ${event.clientY}`,
      on_drag_end: () => last_drag = `end`,
    })}
  >
    <div class="drag-handle">Drag with custom callbacks</div>
    <small style="display: block; opacity: 0.7">this text is not draggable</small>
  </div>
</div>

<p>last pointer: {last_drag || '—'}</p>

highlight_matches

This paragraph will highlight occurrences of the query across element boundaries. Try words like ancient, giant, or split- word matches.

This line is excluded via node_filter.

<script>
  import { highlight_matches } from '$lib'

  let search_text = $state('')
  let disabled = $state(false)

  // Only highlight inside .target; skip any node inside .no-hl
  const node_filter = (node) =>
    node.parentElement?.closest('.no-hl')
      ? NodeFilter.FILTER_REJECT
      : NodeFilter.FILTER_ACCEPT
</script>

<label style="display: inline-flex; gap: 0.6em; align-items: center">
  Search
  <input
    placeholder="type to highlight..."
    bind:value={search_text}
    style="min-width: 16ch"
  />
  <input id="toggle-disabled" type="checkbox" bind:checked={disabled} />
  <label for="toggle-disabled">disabled</label>
</label>

<article
  class="target"
  {@attach highlight_matches({ query: search_text.toLowerCase(), disabled, node_filter })}
>
  <p>
    This paragraph will highlight occurrences of the query across element boundaries. Try
    words like <em>ancient</em>, <strong>giant</strong>, or split-
    <span>word</span> matches.
  </p>
  <p class="no-hl" style="opacity: 0.7">
    This line is excluded via node_filter.
  </p>
</article>

click_outside

<script>
  import { click_outside, tooltip } from '$lib'

  let open_menu = $state(false)
</script>

<div class="menu">
  <button
    class="toggle"
    onclick={() => open_menu = !open_menu}
    {@attach tooltip({ content: 'Toggle menu', placement: 'top' })}
  >
    Menu
  </button>

  {#if open_menu}
    <div
      class="dropdown"
      {@attach click_outside({ exclude: ['.toggle'], callback: () => (open_menu = false) })}
    >
      <ul style="list-style: none; padding: 0; margin: 0">
        <li><a href="#one">First</a></li>
        <li><a href="#two">Second</a></li>
        <li>
          <a href="#noop" class="toggle">Clicking me won’t close (excluded)</a>
        </li>
      </ul>
    </div>
  {/if}
</div>

sortable

PlanetMoonsDiscoveryNotes
Mercury0ancient
Venus0ancientVery bright
Earth1ancientLeads with zeros
Mars21610Phobos/Deimos
Jupiter951610Gas giant
Click headers to sort; click again to reverse
<script>
  import { sortable } from '$lib'
</script>

<table {@attach sortable()} class="demo-table">
  <thead>
    <tr>
      <th>Planet</th>
      <th>Moons</th>
      <th>Discovery</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    {#each [
        { planet: `Mercury`, moons: 0, discovery: `ancient`, notes: `` },
        { planet: `Venus`, moons: 0, discovery: `ancient`, notes: `Very bright` },
        {
          planet: `Earth`,
          moons: 1,
          discovery: `ancient`,
          notes: `Leads with zeros`,
        },
        { planet: `Mars`, moons: 2, discovery: `1610`, notes: `Phobos/Deimos` },
        { planet: `Jupiter`, moons: 95, discovery: `1610`, notes: `Gas giant` },
      ] as
      { planet, moons, discovery, notes }
    }
      <tr>
        <td>{planet}</td>
        <td>{moons}</td>
        <td>{discovery}</td>
        <td>{notes}</td>
      </tr>
    {/each}
  </tbody>
  <caption style="caption-side: bottom; padding-top: 0.5em">
    Click headers to sort; click again to reverse
  </caption>
</table>