npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@klungland/multiselect

v0.1.1

Published

A dependency-light, floating-ui-positioned multiselect component for Svelte 5.

Readme

Multiselect

Published as @klungland/multiselect. A dependency-light multiselect for Svelte 5, positioned with @floating-ui/dom. It flips to the best side when space is tight, and shrinks to fit rather than overflowing the viewport. Icons are Lucide.

Features

  • Boundary aware, flip-then-shrink positioning via @floating-ui/dom. Prefers switching sides over overflowing, and shrinks the dropdown to fit if there's still no room.
  • Search-as-you-type filtering over the option list.
  • Keyboard navigation: arrow keys to highlight, Enter to select, Escape to close.
  • Select all / deselect all (shown by default when maxSelect is unbounded).
  • Free-text option creation via allowUserOptions, with per-context reset via contextKey.
  • Configurable dropdown scrolling: scrollX/scrollY (both default true) cap the dropdown to a fixed size with scrollbars. Set either to false to let it size itself to its content instead, staying viewport-bounded.
  • Works as a single-select (maxSelect={1}) or multi-select.
  • No required styling. The component ships as an unstyled skeleton, with an optional green theme you can opt into.
  • Real dependencies: @floating-ui/dom and @lucide/svelte.

Installation

npm install @klungland/multiselect

Usage

<script lang="ts">
	import { Multiselect } from '@klungland/multiselect';
	import '@klungland/multiselect/theme-green.css'; // optional

	let selected: (string | { label: string; value: string | number })[] = [];

	const options = [
		{ label: 'Apple', value: 'apple' },
		{ label: 'Banana', value: 'banana' },
		{ label: 'Cherry', value: 'cherry' },
	];
</script>

<Multiselect {options} bind:selected maxSelect={null} placeholder="Pick some fruit..." allowUserOptions />

Props

| Prop | Type | Default | Description | | ------------------- | ---------------------------------- | ------- | ----------------------------------------------------------------------------------------- | | options | MultiselectOption[] | — | The selectable options | | selected | MultiselectOption[] (bindable) | [] | Currently selected options | | maxSelect | number \| null | — | Max selections; 1 behaves as single-select, null is unbounded | | minSelect | number | 0 | Minimum selections required, blocks removal below this count | | allowUserOptions | boolean | false | Lets users type and add options not in the provided list | | contextKey | unknown | — | Changing this resets any user-added options (e.g. switching filter context) | | selectAllOption | boolean | — | Force show/hide the "Select all" row; defaults to shown when maxSelect is unbounded | | maxOptions | number \| null | null | Cap how many options are rendered in the dropdown | | width | string | — | Trigger width, as any CSS length ('20rem', '300px', '50%'). Defaults to filling its parent (100%) | | scrollX | boolean | true | Dropdown scrolls horizontally at a fixed (trigger) width. false: sizes to content instead (never narrower than the trigger, free to grow wider), staying viewport-bounded and falling back to scroll only if content doesn't fit | | scrollY | boolean | true | Dropdown scrolls vertically, capped at height. false: grows to fit all options, staying viewport-bounded and falling back to scroll only if content doesn't fit | | height | number | 330 | Pixel cap on dropdown height while scrollY is true. Ignored while scrollY is false | | disabled | boolean | false | Disables all interaction | | loading | boolean | false | Shows a loading message instead of the option list | | placeholder | string | '' | Placeholder text when nothing is selected | | required | boolean | false | Adds a hidden validator input for native form validation | | id | string | — | Element id; also used to derive the dropdown's id | | onChange | () => void | — | Fires on any selection change | | onOpen | () => void | — | Fires when the dropdown opens | | onClose | () => void | — | Fires when the dropdown closes, only if the selection actually changed | | onRemove | () => void | — | Fires when a tag is removed while the dropdown is closed | | onRemoveAll | () => void | — | Fires when all tags are cleared while the dropdown is closed |

MultiselectOption is string | number | { label: string; value: string | number }.

Styling

The component ships unstyled by default: just its shape (pill trigger, rounded dropdown), no colors. Colors come entirely from CSS custom properties, so you can either bring your own or opt into the built-in green theme.

Option A: the built-in green theme

<script>
	import '@klungland/multiselect/theme-green.css';
</script>

This sets every color variable below to a green palette (accent oklch(0.72 0.19 149.6)) with good contrast against a dark background. Importing it applies to every Multiselect on the page. Override individual variables afterwards (globally, or scoped to one instance via an inline style on a wrapper) to adjust it.

Option B: bring your own colors

Set these custom properties yourself (on :root, a wrapper element, or per-instance):

.multiselect {
	--multiselect-bg: #1c1e2b;
	--multiselect-text: #e4e6f1;
	--multiselect-muted: #8a8da3;
	--multiselect-border-color: #34374d;
	--multiselect-hover-bg: #292c40;
	--multiselect-accent: #8b7cf6;
	--multiselect-accent-strong: #6c5ce7;
	--multiselect-accent-contrast: #1c1e2b;
	--multiselect-tag-bg: rgba(139, 124, 246, 0.16);
	--multiselect-tag-text: #cabdfb;
}

| Variable | Used for | | --------------------------------- | ------------------------------------------------------------------ | | --multiselect-bg | Trigger and dropdown background | | --multiselect-text | Primary text | | --multiselect-muted | Placeholder text, chevron, status messages | | --multiselect-border-color | Borders and dividers | | --multiselect-hover-bg | Hover background on options and icon buttons | | --multiselect-accent | Checked checkboxes, hover text color on options | | --multiselect-accent-strong | Reserved for a stronger accent state (e.g. focus rings) in your own overrides | | --multiselect-accent-contrast | Checkmark color drawn on top of --multiselect-accent | | --multiselect-tag-bg | Selected tag chip background | | --multiselect-tag-text | Selected tag chip text |

Shape variables (--multiselect-radius-full, --multiselect-radius-md, --multiselect-radius-sm, --multiselect-z-index, --multiselect-disabled-opacity) have sensible defaults and are independent of theming. Override them the same way if needed.

Development

This repo is the library itself, built with SvelteKit's library mode.

npm install
npm run dev      # demo/playground at src/routes/+page.svelte
npm run check    # svelte-check
npm run build    # builds the demo app + packages the library into dist/

Project structure

src/
  lib/
    Multiselect.svelte    the component
    index.ts              public exports
    types.ts              MultiselectOption / MultiselectProps
    theme-green.css       optional green color theme
    internal/
      floating.ts         floating-ui positioning (scrollX/scrollY logic)
      utils.ts            pure option helpers (getLabel, getValue, sorting)
      IconButton.svelte
      Tag.svelte
      Checkbox.svelte
      OptionRow.svelte
      SelectAllRow.svelte
      StatusMessage.svelte
  routes/
    +page.svelte           demo/dev playground

Credits

Positioning is powered by Floating UI (MIT). Icons are from Lucide (ISC). Both ship as regular dependencies, installed alongside this package rather than bundled into it.

License

MIT. See LICENSE.