Skip to content

janosh/svelte-widgets

Repository files navigation

Svelte Widgets
Svelte Widgets

Tests GitHub Pages NPM version Needs Svelte version Playground Open in StackBlitz

Keyboard-friendly, accessible and highly customizable Svelte components. View the docs

🧩   Components

Every component is a named export from the package root. MultiSelect, CommandMenu, PageSearch, Toc and Masonry also have a direct subpath import (svelte-widgets/Toc.svelte) so bundlers can skip the rest:

Component What it does Docs
MultiSelect Keyboard-friendly multi/single select with grouping, async loading and deep style hooks docs
CommandMenu Command palette with fuzzy search, hotkeys, recents and async actions docs
PageSearch Pagefind-backed site search built on CommandMenu docs
Popover Floating surface that positions, dismisses and traps focus for you docs
ContextMenu Right-click menu anchored to the pointer, with arrow-key navigation docs
Nav Navigation bar with dropdowns, pinning and active-route styling docs
Toc Sticky table of contents that finds and tracks its own headings docs
Masonry Column-balancing masonry grid with SSR support and virtualization docs
CopyButton Copy-to-clipboard button with pending, success and error states docs
ThemeToggle Light/dark/system theme cycler that persists the choice docs
Toggle Accessible switch with a bindable checked docs
CodeExample Collapsible source viewer used by the live examples docs
FileDetails Collapsible <details> viewer for a set of files docs
PrevNext Previous/next links for sequential pages docs
SubpageGrid Card grid linking to child pages docs
Icon Inline SVG icon from the bundled set docs
GitHubCorner The classic corner ribbon link docs
CircleSpinner Minimal loading spinner docs
Wiggle Spring-animated shake wrapper docs

Ten attachments ship alongside them and work on any element. Nine come from svelte-widgets/attachments: click_outside, float, focus_trap, hotkey, tooltip, draggable, resizable, sortable and highlight_matches. The tenth, heading_anchors, has its own svelte-widgets/heading-anchors subpath.

<script>
  import { CommandMenu, MultiSelect, Popover, Toc } from 'svelte-widgets'
</script>

💡   Features

  • No run-time deps: every component needs only Svelte as a peer dependency
  • Keyboard friendly: every interactive component is fully operable without a mouse
  • Bindable: component state is exposed through $bindable props, so you can both read it and drive it from the outside
  • Themeable: CSS variables with sensible defaults on every element, plus prop bags to spread arbitrary attributes onto internals
  • SSR-safe: nothing touches window or localStorage before mount
  • Typed: props, snippets and events are inferred from the data you pass

🧪   Coverage

Statements Branches Lines
Statements Branches Lines

🔨   Installation

npm install --dev svelte-widgets

🚚   Migrating from svelte-multiselect

This package was called svelte-multiselect up to v11. Swap it out:

npm uninstall svelte-multiselect && npm install -D svelte-widgets

Then rewrite the imports. Matching on the opening quote (all three kinds) keeps prose and GitHub URLs untouched, and covers every subpath (/attachments, /utils, /types, /heading-anchors, /live-examples) along with the bare import. It skips .md deliberately: in markdown a backtick-quoted mention is usually prose, not an import.

find src -type f \( -name '*.svelte' -o -name '*.ts' -o -name '*.js' \) -exec perl -pi -e "s{(['\"\`])svelte-multiselect}{\$1svelte-widgets}g" {} +

Three things the rewrite cannot do for you: CmdPalette is now CommandMenu, PagefindPalette is now PageSearch, and click_outside changed shape (it dismisses on pointerdown, and exclude/include merged into one inside option). See the changelog for the details.

Coming from svelte-toc or svelte-bricks instead? Those are now Toc and Masonry here, so the same swap applies with import { Toc } from 'svelte-widgets' and import { Masonry } from 'svelte-widgets'.

📦   Subpath exports

Attachments and utilities are also available as subpath imports:

import {
  click_outside, // dismiss a surface when a press lands outside it
  draggable,
  float, // park an element next to an anchor and keep it there
  focus_trap, // keep Tab inside a surface, hand focus back when it closes
  highlight_matches,
  hotkey, // declarative keybindings, `mod` maps to Cmd or Ctrl
  sortable,
  tooltip,
} from 'svelte-widgets/attachments'
import { compute_position, fuzzy_match, get_label } from 'svelte-widgets/utils'
import { heading_anchors } from 'svelte-widgets/heading-anchors'

Popover and ContextMenu compose these three: a surface positioned by float, dismissed by click_outside (on the press, so a right-click closes it too) and keyboard-scoped by focus_trap.

<script lang="ts">
  import { ContextMenu, Popover } from 'svelte-widgets'
</script>

<Popover placement="bottom" align="start">
  {#snippet trigger(props)}
    <button {...props}>Options</button>
  {/snippet}
  <p>Anything you like in here.</p>
</Popover>

<ContextMenu actions={[{ label: `Reload`, action: () => location.reload() }]}>
  <div>Right-click anywhere in this region</div>
</ContextMenu>

See src/lib/live-examples/readme.md for optional live-example helpers.

🆕   Changelog

View the changelog.

🙏   Contributing

Here are some steps to get you started if you'd like to contribute to this project!

About

Keyboard-friendly, accessible and highly customizable multi-select component

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages