Keyboard-friendly, accessible and highly customizable Svelte components. View the docs
Every component is a named export from the package root and has 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 |
ActionMenu |
Action list opened from a trigger or right-click, with complete menu keyboard semantics | docs |
ConfirmDialog |
Promise-based dialog queue, so two racing prompts can't share one answer | docs |
Dialog |
Native modal with composable sections, close reasons and nested-dialog handling | docs |
DraggablePane |
Floating panel you can drag by its header, resize and reset to its anchor | docs |
NumberRangeInput |
Paired number and range inputs with explicit min, max, and step | docs |
RangeSlider |
Two-handle interval slider with numeric fields, step snapping, RTL, and keyboard controls | docs |
SplitPane |
Resizable panes with ratio or pixel bounds and collapse support | docs |
VirtualList |
Fixed-height row virtualization | docs |
FileInput |
File picker and drop zone with validation, cancellation and retry | docs |
TreeView |
Keyboard-navigable tree with lazy loading and custom node rendering | docs |
JsonTree |
Searchable JSON inspector with editing, copying and diffs | docs |
Progress |
Accessible determinate or indeterminate progress | docs |
TaskStatus |
Task progress and errors with caller-owned cancellation and retry | docs |
SettingsGroup |
Collapsible group for organizing related settings sections | docs |
SettingsSearch |
Settings-row filter that expands matching groups and restores their prior state | docs |
SettingsSection |
Titled settings region with explicit changed keys, resets, descriptions and grid layout | docs |
Sheet |
Dialog-based modal edge panel with side placement and shared dismissal policies | docs |
Tabs |
Controlled ARIA tabs with automatic or manual keyboard activation | docs |
Accordion |
Single or multi-open disclosure group with snippet-rendered content | docs |
FindBar |
In-DOM find-in-page bar that highlights, counts and steps through matches | docs |
CodeBlock |
Read-only code with cancellable highlighting, escaped tokens or trusted HTML | docs |
StatGrid |
Responsive statistic tiles with units, hints and accessible changes | docs |
Spinner |
Loading status with optional text | docs |
StatusMessage |
Dismissible info, success, warning or error feedback | docs |
DragOverlay |
Drop-target overlay with an optional message | docs |
ClickFeedback |
Positioned transient confirmation icon | docs |
CodeEditor |
Virtualized editable code surface with injectable highlighting and persistence | docs |
DiffView |
Virtualized side-by-side and unified diffs with an injectable backend | docs |
Toast |
Notification queue with priorities, dedupe and pause-on-hover | docs |
Nav |
Navigation bar with dropdowns, pinning and active-route styling | docs |
Heading |
Native Svelte heading with an explicit ID and server-rendered anchor | docs |
Toc |
Manifest-backed table of contents with optional dynamic heading tracking | docs |
Masonry |
Column-balancing masonry grid with SSR support and virtualization | docs |
Footer |
Centered row of icon links, sized and themed with --footer-* |
docs |
ActionButton |
Async action button with pending, success and error feedback | docs |
CopyButton |
Copy-to-clipboard button with success and error feedback | docs |
ButtonGroup |
Segmented control over a set of options, single or multi select | docs |
FullscreenButton |
Fullscreen toggle scoped to one wrapper, so viewers don't fight over the flag | docs |
ThemeToggle |
Light/dark/system theme cycler with persistence and cross-tab synchronization | 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 |
ContributorList |
Avatar row of GitHub contributors | docs |
LiteYouTubeEmbed |
YouTube poster that only loads the player iframe once clicked | docs |
Wiggle |
Spring-animated shake wrapper | docs |
Fifteen attachments work on any element: fourteen come from svelte-widgets/attachments, while heading_anchors has its own subpath. dismiss_on_outside_press is the lower-level multi-surface primitive behind click_outside.
<script>
import { CommandMenu, MultiSelect, Popover, Tabs, Toc } from 'svelte-widgets'
</script>- Lightweight components: core widgets need only Svelte; Markdown uses Marked and js-yaml, while math and syntax highlighting use optional peers
- Keyboard friendly: every interactive component is fully operable without a mouse
- Bindable: component state is exposed through
$bindableprops, 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: components support server rendering; browser-only helpers run on the client
- Typed: props, snippets and events are inferred from the data you pass
The unit CI job reports current coverage and enforces the thresholds in vite.config.ts.
npm install -D svelte-widgetsCustom library APIs use snake_case. Native DOM handlers such as onclick, oninput, and onchange keep their browser names and receive DOM events. In particular, replace MultiSelect's former custom onchange with on_change to keep receiving selection details.
| Previous API | Replacement |
|---|---|
Custom props and snippets such as searchText, maxSelect, selectedItem |
search_text, max_select, selected_item; apply snake_case throughout custom props and snippet fields |
MultiSelect onchange, onadd, onremove |
on_change, on_add, on_remove; native onchange receives a DOM event |
loadOptions, debounceMs, batchSize, result hasMore |
load_options, debounce_ms, batch_size, result has_more |
Editor backend fields such as docId, requestId, startLine, oldText |
doc_id, request_id, start_line, old_text; update both request and response payloads |
| Optional, numeric, or repeated command action IDs | Required, unique, nonempty string IDs across each menu, including sections and loaded pages; convert numeric IDs explicitly and resolve collisions. CmdSection.selected is a string or null when supplied |
CommandMenu.onadd and selection/creation controls |
on_execute({ action }); command menus execute one action without retaining selection |
MultiSelect history, undo, redo, canUndo, canRedo, onundo, onredo |
Manage selection history in the caller using bind:value |
MultiSelect parseLabelsAsHtml, activeOptionFallbackKey |
Use option / selected_item snippets for custom rendering and a stable key function for option identity |
NumberRangeInput.schema |
Pass explicit min, max, and step values from the schema |
print_element(node, { single_page, page_width_mm, px_per_inch, filename }) |
print_page({ filename }) prints the whole page; use @media print CSS for visibility and pagination |
/live-examples, /live-examples/create-highlighter, /katex |
/markdown, /markdown/vite, /highlight; see the Markdown migration guide |
Additional API changes:
- MultiSelect uses
bind:valuealone:mode="single"takes one option ornull, and the defaultmode="multiple"takes an array. - SettingsSection takes
changed_keysandon_reset_key(key). Value comparison and reset defaults belong to the caller. - Tooltips accept plain text and hover/focus triggers. Use Popover for formatted content, controls, and application-controlled visibility.
- Native Svelte headings use
Headingwith an explicit ID; removeheading_ids()from preprocessors. Toc consumesitemsmetadata or discovers existing IDs withdynamic; invalid selectors and collapse modes throw.
Command IDs are compared exactly, without coercion or trimming. Empty and whitespace-only strings are rejected; action labels and section titles may repeat. Preserve a section object when reordering it to retain its rendered nodes.
This package was called svelte-multiselect up to v11 (#432). Swap it out:
npm uninstall svelte-multiselect && npm install -D svelte-widgetsThen rewrite the imports. Matching on the opening quote (all three kinds) keeps prose and GitHub URLs untouched, and covers every subpath 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 and PagefindPalette is now PageSearch (#428), and click_outside changed shape (it dismisses on pointerdown, and exclude/include merged into one inside option) (#431). See the changelog for the details.
Coming from svelte-toc or svelte-bricks instead? Those are now Toc and Masonry here (#432), so the same swap applies with import { Toc } from 'svelte-widgets' and import { Masonry } from 'svelte-widgets'.
Components have direct .svelte entry points, and headless/build-time APIs have focused subpaths:
import {
auto_update_position, // coalesce floating-position updates and clean up listeners
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
register_escape_layer, // add a handler to the shared LIFO Escape stack
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'| Subpath | API |
|---|---|
/attachments |
Element attachments and dismissal primitives |
/canvas |
Parent content-box sizing, DPR tracking and coalesced canvas redraws |
/csv |
CSV escaping and row serialization with optional explicit columns |
/format |
Binary byte-size formatting |
/roving-focus |
One keyboard tab stop across available HTML or SVG items, including nested groups |
/stats |
Statistic value and change formatting |
/url-params |
Typed query validation and URL updates that omit defaults |
/clipboard |
Clipboard feedback state |
/code-editor |
Backend-agnostic editing, diff rendering and primitives |
/code-editor/editor.css |
Shared syntax-token and diff-view styles |
/dialogs |
Queued choice, confirmation and prompt requests |
/file-drop |
Directory expansion and accept filtering |
/find-in-page |
Reactive find-in-page cursor behind FindBar |
/fullscreen |
Shared fullscreen state |
/heading-anchors |
Heading text/ID helpers, slugger and anchor attachment |
/image-markup |
Image-fit geometry and canvas rendering of freehand annotation strokes |
/icons |
Dynamic icon registry |
/json-tree |
JSON inspector component and types |
/json-tree/path |
Dot/bracket path formatting and resolution |
/json-tree/utils |
JSON traversal, immutable path edits, search and diff helpers |
/labels |
Default UI strings for i18n, incl. attachments & helpers |
/highlight |
Lazy default and custom grammar highlighters |
/markdown |
Markdown-to-Svelte preprocessor and direct HTML renderer |
/markdown/vite |
Live code examples with virtual modules and hot reload |
/markdown/content |
Content manifests, typed frontmatter, link validation, TOC and search records |
/markdown/check |
Node-only syntax, type and assertion checks for documentation examples |
/print |
Page printing with a suggested PDF filename |
/source-links |
Link inline code mentions of your source to GitHub |
/source-links/vite-plugin |
Vite plugin emitting the file/export index those links use |
/source-links/virtual |
Types for the plugin's virtual:source-symbols module |
/storage |
Non-throwing localStorage, persisted choices and MRU lists |
/text-search |
Text ranges, highlighting and search-jump helpers |
/theme |
Headless light/dark/system state |
/toast-queue |
Toast reducer and reactive store |
/utils |
Positioning, fuzzy matching, hotkeys and general helpers |
/virtual |
Visible-window calculation for fixed-size items |
/vite-config |
This repository's Vite Plus configuration helper |
/assets |
Svelte preprocessor for relative media, responsive images and downloads |
/yaml |
Vite YAML loader with build-time data transformation |
create_canvas_surface() owns both layers' inline CSS dimensions and restores them on cleanup. Supply height() or give the parent a definite height; draw callbacks receive CSS-pixel coordinates and isolated context state. create_roving_focus() keeps nested groups independent and observes DOM eligibility changes, including hidden panels and disabled items.
StatGrid changes are neutral by default; set an item's delta_tone to positive or negative when the change has that meaning. ClickFeedback restarts when given a fresh position object, even at identical coordinates. rows_to_csv(rows, columns) accepts explicit readonly columns for sparse rows or header-only exports. URL validators accept native Sets or record keys; present empty strings remain valid when allowed.
CodeEditor and DiffView take host-supplied EditorBackend and DiffBackend implementations, either through their backend props or once per app with set_editor_backend() and set_diff_backend(). Import svelte-widgets/code-editor/editor.css alongside them for the token palette and shared line metrics. The editor takes a host-owned model={create_editor_model({ uri, text })} whose rope, UTF-16 selection, transactions, dirty checkpoint, and bounded history remain usable at 100 MB / 1,000,000 lines. Saving is an optional callback, so file reads, persistence, conflicts and draft policy remain in the host. The editable DOM uses a viewport-sized textarea; explicit selections may expand that window, while scroll height remains subject to browser limits. Both backend contracts are runtime-agnostic and can call a native process, worker, WASM module or server route.
Run the opt-in, hardware-sensitive editor stress target locally with RUN_LARGE_EDITOR_TESTS=1 npx vitest run tests/vitest/code-editor-model.test.ts; normal CI deliberately skips it.
Use markdown() for Markdown pages with YAML frontmatter, embedded Svelte, GFM tables and task lists. Enable math for KaTeX. Markdown renders heading IDs and anchor links in the initial HTML; use the Heading component with an explicit id for native Svelte pages:
import { create_markdown, markdown } from 'svelte-widgets/markdown'
import { asset_imports } from 'svelte-widgets/assets'
export default {
extensions: [`.svelte`, `.md`],
preprocess: [markdown(create_markdown({ math: true })), asset_imports()],
}<script lang="ts">
import { Heading, Toc } from 'svelte-widgets'
const headings = [{ id: `overview`, title: `Overview`, level: 2 }]
</script>
<Toc items={headings} />
<Heading id="overview">Overview</Heading>Heading links render in the initial HTML. Reveal them with opacity on hover or focus so their space stays reserved. Heading takes level, id, and link={false} to suppress a link. Markdown owns its generated IDs and anchors; create_markdown({ heading_links: false }) emits IDs alone. Toc consumes heading metadata for its initial render; opt into dynamic for DOM-discovered content. The optional heading_anchors() attachment enhances dynamically inserted headings.
Use engine.render(source, { filename }) for HTML strings, or parse once with engine.parse(source, { dialect: "markdown" }) and pass the document to render_markdown() when you also need its manifest. Set frontmatter: false in create_markdown() for embedded data fields whose leading --- should remain Markdown. Access frontmatter as metadata.title; fence settings are validated during parsing. Use check_document(document, options) from /markdown/check for one-shot documentation checks. Use assert_ok() to unwrap results at build boundaries and markdown_vite(engine) for runnable code fences. See the Markdown API for configuration and migration details. Import katex/dist/katex.min.css once when enabling math.
asset_imports() resolves relative media URLs, srcset candidates, PDF links and download links through Vite, preserving query strings and fragments. Place it after Markdown preprocessing so authored Markdown images are included. Dynamic URLs, component props, public-root paths and external URLs remain unchanged. Filenames containing # or ? must be renamed because Vite interprets those characters as URL delimiters, even when encoded in the authored URL.
Add yaml_plugin() from svelte-widgets/yaml to Vite's plugins to import .yaml, .yml and YAML citation files (.cff) as default-exported data. Its YAML 1.2 core schema keeps dates as strings. Destructure the default export instead of using named imports. An optional transform(data, filename) callback can validate or asynchronously enrich data at build time, including rendering Markdown fields; return the data to export. Invalid YAML, cycles and non-JSON values fail with filename context. Vite handles explicit ?raw and ?url imports.
Popover and ActionMenu use the browser Popover API for top-layer rendering, light dismissal and Escape handling, while float supplies placement. Explicit custom dismissal policies still use click_outside. Dialog-like popovers can add focus_trap; action menus use Arrow/Home/End navigation and close on Tab so browser focus continues in page order.
<script lang="ts">
import { ActionMenu, Popover } from 'svelte-widgets'
const actions = [{ label: `Reload`, action: () => location.reload() }]
</script>
<Popover placement="bottom" align="start">
{#snippet trigger(props)}
<button {...props}>Options</button>
{/snippet}
<p>Anything you like in here.</p>
</Popover>
<ActionMenu {actions}>
{#snippet trigger(props)}
<button {...props}>Page actions</button>
{/snippet}
</ActionMenu>
<ActionMenu {actions}>
<div>Right-click anywhere in this region</div>
</ActionMenu>See the Markdown guide for highlighting and runnable examples.
Docs that mention source files or exports in inline code (`Footer`, `make_config`) can link them to the GitHub line they live on, pinned to the commit the site was built from. Add the plugin to vite.config.ts, reference its virtual-module types from src/app.d.ts and attach the linker to the element that wraps your pages:
// vite.config.ts
import source_links from 'svelte-widgets/source-links/vite-plugin'
export default { plugins: [sveltekit(), source_links()] } // indexes src/lib by default
// src/app.d.ts
/// <reference types="svelte-widgets/source-links/virtual" />
// src/site/source-links.ts
import { create_source_links } from 'svelte-widgets/source-links'
import * as source_symbols from 'virtual:source-symbols'
export const { link_source_mentions, source_href } = create_source_links(source_symbols)<main {@attach link_source_mentions}>{@render children()}</main>Only exact, unambiguous names link: a file name or bare component name (Footer, utils.ts) points at the file, an exported definition (make_config) at its line, and names defined in several files (index.ts) or that aren't source (label) are left alone. source_href(name) gives the same URL for use in your own markup.
Here are some steps to get you started if you'd like to contribute to this project!