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

@juspay/svelte-ui-components

v4.33.3

Published

A themeable Svelte 5 UI component library with CSS custom property driven styling

Readme

@juspay/svelte-ui-components

A themeable Svelte 5 component library where every visual property is a CSS custom property. Build any design system on top — no source changes needed.

npm install @juspay/svelte-ui-components

Requires svelte ^5.41.2 and type-decoder ^2.1.0 as peer dependencies.


Why This Library?

Most component libraries ship with a fixed look. Changing it means fighting overrides, patching internals, or forking.

This library takes a different approach: components are unstyled by default and expose every visual decision — colors, spacing, typography, borders, shadows, radii — as CSS custom properties. You define the design system. The components render it.

<!-- A button that looks however you want -->
<div class="my-theme">
  <Button text="Continue" onclick={handleClick} />
</div>

<style>
  .my-theme {
    --button-color: #000;
    --button-text-color: #fff;
    --button-border-radius: 8px;
    --button-padding: 12px 24px;
    --button-font-size: 14px;
    --button-font-weight: 600;
    --button-hover-color: #222;
    --button-border: 1px solid #333;
  }
</style>

Breaking changes, and the release they actually shipped in

Both changes below shipped in 3.5.1, which went out as a patch by mistake. The release workflow picks the version from the newest commit alone, and the commit that made the build green was a fix: — so the breaking commit beneath it was never seen. 4.0.0 is that same content under the version number it should have carried. A ^3.5.0 range already resolves to it; 3.5.0 is the last release without these changes.

Every deprecated event-prop spelling is gone. The 190 camelCase and mixed-case event props that 3.x accepted as aliases are now unknown props, which Svelte drops without an error — the handler simply stops running. npx sui-codemod ./src rewrites them, and docs/MIGRATION_4.0.md lists every pair.

children is no longer a declared property on sui-chat-bubble, sui-draggable and sui-resizable. Assigning it sets an inert expando and the content silently disappears. Pass the content as light-DOM children instead, which those wrappers forward through their default slot:

el.children = mySnippet; // before — no longer works
<sui-draggable><div>…</div></sui-draggable>
<!-- after -->

Reading element.children is what the change fixes. Declaring the property replaced the inherited accessor, so up to 3.5.0 element.children returned undefined on those three elements rather than an HTMLCollection, and el.children.length threw. Removing the declaration gives the collection back.

You can find affected call sites with a dry run — this reports and writes nothing:

npx sui-codemod --dry-run ./src

It flags .children = assignments only in files that also mention one of the three elements, so ordinary DOM code is not reported. The fix moves content from a JavaScript assignment into markup, which is a decision about where content is authored, so the tool deliberately does not rewrite it for you. Requires Node ≥ 22.18; typescript is an optional peer dependency that the codemod's Svelte-parsing paths need.

Nothing else in the custom-element surface breaks. Everything this release adds — 185 wrapper props, three previously unregistered elements, the spellcheck and aria-haspopup mappings, the snippet props on Banner, ListItem, Menu, Modal and Button, Accordion's disabled, LottiePlayer's callbacks — is new surface or a fix to something that did not work.


Quick Start

<script lang="ts">
  import { Button, Input, Toggle, Toast } from '@juspay/svelte-ui-components';
</script>

<!-- Basic button -->
<Button text="Submit" onclick={() => console.log('clicked')} />

<!-- Input with validation -->
<Input
  value=""
  placeholder="Enter email"
  dataType="email"
  onstatechange={(state) => console.log(state)}
/>

<!-- Toggle switch -->
<Toggle checked={false} text="Dark mode" onclick={(val) => console.log(val)} />

Components

Inputs & Form Controls

| Component | Description | Docs | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | Button | Action trigger with circular loader, progress bar, icon/children snippets, and aria-expanded support. | docs | | Input | Text field with built-in validation for email, phone, password, and custom patterns. Supports text transformers and textarea mode. | docs | | InputButton | Input field fused with action buttons — for search bars, OTP entry, coupon codes. | docs | | Select | Dropdown picker with single-select (with search), multi-select (checkboxes + Select All + Apply), and custom content slots. | docs | | Toggle | Labeled on/off switch with sliding ball animation. | docs | | Checkbox | Styled checkbox input with custom SVG checkmark. | docs | | Radio | Styled radio button with custom circular indicator. | docs | | Slider | Range slider with configurable min, max, step, and optional value display. | docs | | Choicebox | Selectable option group with single-select (radio) or multi-select (checkbox) behavior and custom content. | docs | | Label | A real <label> element — clicking the text focuses (and for a checkbox or radio, activates) the control named by for, with an optional accessible required marker. | docs | | RatingGroup | Star rating input — a single role="slider" tab stop driven by arrow keys, Home/End or a click, with optional half stars and native form participation. | docs |

Display & Data

| Component | Description | Docs | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | AnimatedNumber | Per-digit odometer that rolls a number to its new value. Takes a number formatted through Intl.NumberFormat, or an already-formatted string. | docs | | Avatar | Circular avatar with image (Img with fallback) or text initial. | docs | | Badge | Icon with a numeric/text badge overlay in the corner. | docs | | GridItem | Grid cell with icon, label, and loading overlay animation. | docs | | Icon | Clickable icon with optional text label. | docs | | IconStack | Layered horizontal stack of overlapping circular icons/avatars. | docs | | Img | Image with automatic fallback on load error. | docs | | ListItem | Multi-section list row with images, labels, and accordion expansion. | docs | | Pill | Compact label/tag for status or categories, optionally clickable with a11y. | docs | | Status | Full-screen status display for success/failure screens. | docs | | Table | Data table with keyed columns, built-in cell renderers, sorting, pagination and selection. | docs | | RelativeTime | Auto-updating relative time display ("5 minutes ago") with locale support and optional tooltip. | docs | | AspectRatio | Constrains content to a fixed width-to-height ratio using CSS aspect-ratio, reserving layout space before an image, video or iframe loads. | docs |

Feedback & Loading

| Component | Description | Docs | | --------------- | ----------------------------------------------------------------------------------------------- | --------------------------- | | Banner | Sticky notification banner with icon snippet, dismiss button, link text, and click interaction. | docs | | BrandLoader | Full-screen branded splash/loading animation. | docs | | Loader | Circular spinner with gradient foreground. | docs | | LoadingDots | Animated inline dot sequence with bounce/pulse animations. | docs | | Shimmer | Loading placeholder with animated shimmer effect. All visuals via CSS variables. | docs | | Toast | Animated slide-in notification with type variants and auto-dismiss. | docs | | Progress | Horizontal progress bar with animated fill. | docs | | Gauge | Semicircular gauge/meter with configurable segments and animated value. | docs |

Overlays & Panels

| Component | Description | Docs | | -------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------- | | Modal | Dialog overlay with configurable size, alignment, header, footer, transitions, and back-press support. | docs | | ModalAnimation | Fly/fade transition wrapper for modal content. | docs | | OverlayAnimation | Fade transition wrapper for overlay backgrounds. | docs | | Sheet | Slide-in panel from any edge (left/right/top/bottom) with header, scrollable content, footer, and focus trap. | docs | | CommandMenu | Command palette (Ctrl+K) with fuzzy search, grouped commands, and keyboard navigation. | docs | | ContextMenu | Right-click context menu with nested submenus, separators, and keyboard navigation. | docs | | Menu | Dropdown action menu with keyboard navigation, typeahead, disabled/danger items, and separators. | docs | | Tooltip | Hover-triggered tooltip with configurable position and delay. | docs |

Navigation & Structure

| Component | Description | Docs | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | Accordion | Expandable/collapsible container with CSS grid animation. | docs | | Carousel | Horizontal content slider with swipe and pagination dots. | docs | | CheckListItem | Checklist row with checkbox, label, and toggleable checked state. | docs | | Pagination | Page navigation with previous/next buttons and numbered page links. | docs | | Scroller | Overflowing item list with arrow navigation, gradient edges, drag-to-scroll, and snap support. | docs | | Stepper | Multi-step progress indicator with completed, active, and pending states. | docs | | Step | Individual step within a Stepper — renders number, label, and connector. | docs | | Tabs | Tabbed interface with animated active indicator. | docs | | Toolbar | Fixed header bar with back button, title, and customizable content areas. | docs | | Separator | Horizontal or vertical divider — decorative and out of the accessibility tree by default, role="separator" when it demarcates meaningful sections. | docs |

Actions

| Component | Description | Docs | | ----------------- | ------------------------------------------------------------------------- | ----------------------------- | | Snippet | Copyable command-line snippet with prompt prefix and copy button. | docs | | SplitButton | Primary action button with dropdown secondary actions via Menu component. | docs | | KeyboardInput | Keyboard shortcut display with styled key caps. | docs | | ThemeSwitcher | Segmented control for light/dark/system theme switching. | docs |

Decorative

| Component | Description | Docs | | ----------- | ------------------------------------------------ | ----------------------- | | Book | 3D book display with animated page turning. | docs | | Browser | Browser window chrome frame for content display. | docs | | Phone | Smartphone device frame for app previews. | docs |


Web Components

Every component also ships as a real Custom Element (Svelte 5's native customElement compiler output, shadow DOM), tagged sui-* — usable from React, Vue, Angular, or plain HTML, no separate Svelte installation required; the runtime is bundled with each component.

<!-- Always the latest release, via GitHub Pages -->
<script type="module" src="https://juspay.github.io/svelte-ui-components/wc/index.js"></script>

<!-- Pinned to an npm version, via jsDelivr or unpkg -->
<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@juspay/svelte-ui-components@latest/dist-wc/index.js"
></script>
<script
  type="module"
  src="https://unpkg.com/@juspay/svelte-ui-components@latest/dist-wc/index.js"
></script>

<sui-button>Click me</sui-button>
<sui-input placeholder="Type here"></sui-input>

Or from npm directly, via the package's ./wc export:

import '@juspay/svelte-ui-components/wc';

Theming works identically to the Svelte components — the same --{component}-{element}-{property} CSS custom properties apply across the shadow DOM boundary.

Event handlers: use addEventListener, not element.onclick

Handlers are passed as props, and 93 of those props are named after event-handler accessors that already exist on HTMLElement — onclick, onchange, oninput, onkeydown, ontouchstart and 22 others. On a custom element the component's prop wins, so assigning the property does not register a DOM handler the way it would anywhere else:

const checkbox = document.querySelector('sui-checkbox');

// Sets the component's prop. On sui-checkbox and sui-toggle that prop is called
// with a boolean, so `event` here is `true` / `false`, not a MouseEvent.
checkbox.onclick = (event) => console.log(event.clientX); // undefined

// Always does what you expect.
checkbox.addEventListener('click', (event) => console.log(event.clientX));

Two things are not affected: the inline HTML attribute (<sui-button onclick="...">) never used this accessor, and addEventListener is untouched. Only the JavaScript property assignment differs.

Renaming these is a breaking change to every element's JavaScript API, so the whole set has to move at once at a major — the same way the aria* collisions were renamed in 4.0.0. Until then the existing declarations are recorded in scripts/wc-parity/prop-parity.test.ts, which fails the build on a new one.


Theming

How It Works

Every component reads its visual properties from CSS custom properties with sensible defaults. To create a theme, define the variables on any ancestor element:

/* theme.css */
.my-design-system {
  /* Button */
  --button-color: #0070f3;
  --button-text-color: #fff;
  --button-border-radius: 6px;
  --button-padding: 10px 20px;
  --button-font-family: 'Inter', sans-serif;
  --button-font-size: 14px;
  --button-hover-color: #0060df;

  /* Input */
  --input-background: #fafafa;
  --input-border: 1px solid #eaeaea;
  --input-radius: 6px;
  --input-font-family: 'Inter', sans-serif;
  --input-focus-border: 1px solid #0070f3;

  /* Toast */
  --toast-border-radius: 8px;
  --toast-font-family: 'Inter', sans-serif;
  --toast-success-background-color: #0070f3;

  /* Modal */
  --modal-border-radius: 12px;
  --modal-content-background-color: #fff;
  --background-color: #00000066;
}
<div class="my-design-system">
  <Button text="Save" onclick={save} />
  <Input value="" placeholder="Search..." />
</div>

Dark Mode

The library ships a dark theme. Import it once and every component follows data-theme="dark" on the document root — which is what ThemeSwitcher already writes:

import '@juspay/svelte-ui-components/theme-dark.css';
<script>
  import { ThemeSwitcher } from '@juspay/svelte-ui-components';
</script>

<ThemeSwitcher
  mode="segment"
  onchange={(_, resolved) => {
    document.documentElement.dataset.theme = resolved;
  }}
/>

The stylesheet only re-declares custom properties, so it composes with your own theme rather than replacing it: anything you set on a more specific ancestor still wins, and any variable you have not set keeps the library's dark value.

It also works for the <sui-*> custom elements. Custom properties inherit across shadow boundaries, which is the only route into a shadow root from outside — a selector-based override such as [data-theme='dark'] .my-part { … } applies in the Svelte build and is dead for the web component. Theme through variables and both work.

Scoped Theming

Because CSS variables cascade, you can scope different themes to different parts of your app:

<div class="light-theme">
  <Button text="Light" onclick={handleClick} />
</div>

<div class="dark-theme">
  <Button text="Dark" onclick={handleClick} />
</div>

<style>
  .light-theme {
    --button-color: #fff;
    --button-text-color: #000;
    --button-border: 1px solid #eaeaea;
  }
  .dark-theme {
    --button-color: #111;
    --button-text-color: #fff;
    --button-border: 1px solid #333;
  }
</style>

Bridging Your Own Token Scale

The library names a property per component element (--{component}-{element}-{property}), so components render correctly with no stylesheet at all. Most design systems name the opposite thing: a short scale of values — a handful of neutrals, lines and radii — composed everywhere.

Restating a scale as hundreds of per-component declarations, and keeping them in step by hand, is not the intended cost. Define your scale once and declare the component tokens in terms of it; the cascade does the rest, and a value changes in one place:

/* Your scale — the only values you maintain. */
:root {
  --acme-surface-1: #ffffff;
  --acme-surface-2: #f4f5f7;
  --acme-line-2: #d6d9de;
  --acme-ink-1: #1a1d21;
  --acme-ink-2: #5b6371;
  --acme-accent: #3b5bdb;
  --acme-radius-md: 8px;
}

[data-theme='dark'] {
  --acme-surface-1: #14161a;
  --acme-surface-2: #1e2126;
  --acme-line-2: #333942;
  --acme-ink-1: #e6e8eb;
  --acme-ink-2: #9aa3b2;
  --acme-accent: #748ffc;
}

/* The bridge — component tokens expressed as your scale. Write it once. */
:root {
  --button-color: var(--acme-accent);
  --button-text-color: var(--acme-surface-1);
  --button-border-radius: var(--acme-radius-md);
  --button-secondary-text-color: var(--acme-ink-1);
  --button-secondary-border-color: var(--acme-line-2);

  --input-background: var(--acme-surface-1);
  --input-text-color: var(--acme-ink-1);
  --input-border: 1px solid var(--acme-line-2);
  --input-placeholder-color: var(--acme-ink-2);

  --table-header-background: var(--acme-surface-2);
  --table-content-color: var(--acme-ink-1);
  --table-border: 1px solid var(--acme-line-2);
}

Note that a few tokens take a full shorthand rather than a bare colour — --input-border and --table-border above are 1px solid …, not …. Setting one to a colour alone produces an invalid declaration and no border at all, which is silent. Each component's page in docs/ gives the shape it expects.

Both themes now come from seven values. Because the bridge only re-declares custom properties it composes with the shipped dark theme rather than fighting it: import theme-dark.css for the components you have not bridged, and your own declarations win wherever they are more specific.

The same file works for the <sui-*> elements, since custom properties are the one thing that crosses a shadow boundary. A rule that reaches inside a component — [data-theme='dark'] .my-part { … } — silently does nothing there.

CSS Variable Reference

Each component documents its full set of CSS variables in the docs/ directory. The variable naming convention follows the pattern:

--{component}-{element}-{property}

For example:

  • --button-color — button background color
  • --input-error-msg-text-color — input error message text color
  • --modal-footer-primary-button-border-radius — border radius of the primary button inside a modal footer
  • --toast-success-background-color — background color for success variant toasts

Props & Types

Components use a typed props pattern split into mandatory, optional, and event properties:

// Every component follows this pattern
type ButtonProperties = OptionalButtonProperties & ButtonEventProperties;

type OptionalButtonProperties = {
  text?: string;
  enable?: boolean;
  disabled?: boolean;
  showLoader?: boolean;
  loaderType?: 'Circular' | 'ProgressBar';
  type?: 'submit' | 'reset' | 'button';
  icon?: Snippet;
  children?: Snippet;
  ariaLabel?: string;
  ariaExpanded?: boolean;
  // ...
};

type ButtonEventProperties = {
  onclick?: (event: MouseEvent) => void;
  onkeyup?: (event: KeyboardEvent) => void;
};

All types are exported from the package:

import type {
  ButtonProperties,
  InputProperties,
  ModalProperties,
  SelectProperties,
  ToastProperties,
  MenuItem,
  SheetSide
  // ...
} from '@juspay/svelte-ui-components';

Svelte 5 Patterns

This library is built on Svelte 5 and uses its modern APIs:

  • $props() for declaring component properties
  • $state() and $derived() for reactive state
  • $bindable() for two-way bound props (e.g., Sheet.open, Banner.visible, Button.showProgressBar)
  • Snippet for composable content slots (e.g., Button.icon, Sheet.content, Banner.rightContent)
<script lang="ts">
  import { Sheet } from '@juspay/svelte-ui-components';

  let open = $state(false);
</script>

<button onclick={() => (open = true)}>Open</button>

<Sheet bind:open title="Settings" side="right">
  {#snippet content()}
    <p>Sheet body goes here.</p>
  {/snippet}
  {#snippet footer()}
    <button onclick={() => (open = false)}>Done</button>
  {/snippet}
</Sheet>

MCP Server

An MCP (Model Context Protocol) server ships as a separate package for AI-assisted development:

npm install @juspay/svelte-ui-components-mcp

It provides full component documentation — props, events, CSS variables, type references — through a standard MCP tool interface. Useful for integrating component knowledge into AI coding assistants.


Development

pnpm install       # install dependencies
pnpm dev           # start dev server with hot reload
pnpm build         # build the library (vite + svelte-package + publint)
pnpm test          # run integration + unit tests
pnpm lint          # check formatting and lint rules
pnpm format        # auto-format source files

Project Structure

src/lib/
  {Component}/
    {Component}.svelte     # component implementation
    properties.ts          # prop type definitions
  types.ts                 # shared types (ValidationState, InputDataType, etc.)
  utils.ts                 # shared utilities (validateInput, prefersReducedMotion, body scroll lock)
  index.ts                 # public exports

docs/                      # component documentation (one markdown file per component)
mcp/                       # MCP server package (separate npm package)

Release

Pushing to the release branch triggers an automated CI pipeline:

  1. Lints the codebase
  2. Determines semver bump from conventional commit message (feat: = minor, fix: = patch, ! = major)
  3. Bumps package.json version and generates a changelog
  4. Builds the package
  5. Creates a GitHub release with a git tag
  6. Publishes to npm (--access public)

License

MIT