Skip to content

Latest commit

Β 

History

457 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Svelte Widgets
Svelte Widgets

CI 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 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>

πŸ’‘   Features

  • 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 $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: components support server rendering; browser-only helpers run on the client
  • Typed: props, snippets and events are inferred from the data you pass

πŸ§ͺ   Coverage

The unit CI job reports current coverage and enforces the thresholds in vite.config.ts.

πŸ”¨   Installation

npm install -D svelte-widgets

Migrating to 1.8

Custom 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:value alone: mode="single" takes one option or null, and the default mode="multiple" takes an array.
  • SettingsSection takes changed_keys and on_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 Heading with an explicit ID; remove heading_ids() from preprocessors. Toc consumes items metadata or discovers existing IDs with dynamic; 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.

🚚   Migrating from svelte-multiselect

This package was called svelte-multiselect up to v11 (#432). 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 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'.

πŸ“¦   Subpath exports

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.

πŸ†•   Changelog

View the changelog.

πŸ™   Contributing

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

Releases

Used by

Contributors

Languages