svelte-widgets
v1.2.0
Published
Accessible, keyboard-friendly Svelte components and attachments
Maintainers
Readme
🧩 Components
Every component is a named export from the package root, and every one also 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 |
| ContextMenu | Right-click menu anchored to the pointer, with arrow-key navigation | docs |
| ConfirmDialog | Promise-based dialog queue, so two racing prompts can't share one answer | docs |
| DraggablePane | Floating panel you can drag by its header, resize and reset to its anchor | docs |
| Sheet | Portalled edge panel with backdrop dismissal and focus restoration | docs |
| Tabs | Controlled ARIA tabs with automatic or manual keyboard activation | docs |
| Accordion | Single or multi-open disclosure group with snippet-rendered content | docs |
| Toast | Notification queue with priorities, dedupe and pause-on-hover | 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 |
| Footer | Centered row of icon links, sized and themed with --footer-* | docs |
| CopyButton | Copy-to-clipboard button with pending, success and error states | 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 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 |
| ContributorList | Avatar row of GitHub contributors, grayscale until hover | docs |
| LiteYouTubeEmbed | YouTube poster that only loads the player iframe once clicked | docs |
| Wiggle | Spring-animated shake wrapper | docs |
Fifteen attachments ship alongside them and work on any element. Fourteen come from svelte-widgets/attachments: backdrop_dismiss, click_outside, contrast_color, draggable, file_drop, float, focus_trap, forward_window_keydown, highlight_matches, hotkey, portal, resizable, sortable and tooltip. The fifteenth, heading_anchors, has its own svelte-widgets/heading-anchors 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
- 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
$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: nothing touches
windoworlocalStoragebefore mount - Typed: props, snippets and events are inferred from the data you pass
🧪 Coverage
| 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-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,
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
Components have direct .svelte entry points, and headless/build-time APIs have focused subpaths:
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'| Subpath | API |
| ----------------------------------- | ---------------------------------------------------------- |
| /attachments | Element attachments and dismissal primitives |
| /clipboard | Clipboard feedback state |
| /dialogs | Queued choice, confirmation and prompt requests |
| /file-drop | Directory expansion and accept filtering |
| /fullscreen | Shared fullscreen state |
| /heading-anchors | Heading ID preprocessor, slugger and anchor attachment |
| /icons | Dynamic icon registry |
| /katex | KaTeX before/after preprocessor pair |
| /live-examples | mdsvex live-example transform, Vite plugin and highlighter |
| /live-examples/create-highlighter | Lightweight custom grammar highlighter factory |
| /print | Element printing |
| /text-search | Text ranges, highlighting and search-jump helpers |
| /theme | Headless light/dark/system state |
| /toast-queue | Toast reducer and reactive store |
| /types | Shared component/action types |
| /utils | Positioning, fuzzy matching, hotkeys and general helpers |
| /vite-config | This repository's Vite Plus configuration helper |
For $…$ and $$…$$ math in mdsvex, run katex_preprocess() before and after mdsvex so markdown never sees the rendered HTML, then heading_ids() last:
import { mdsvex } from 'mdsvex'
import { heading_ids } from 'svelte-widgets/heading-anchors'
import { katex_preprocess } from 'svelte-widgets/katex'
const { before: katex_before, after: katex_after } = katex_preprocess()
export default {
preprocess: [katex_before, mdsvex({ extensions: [`.md`] }), katex_after, heading_ids()],
}Import katex/dist/katex.min.css once in the app so the generated markup is styled.
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
🙏 Contributing
Here are some steps to get you started if you'd like to contribute to this project!
