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

crusher-ui-kit

v1.5.0

Published

Token-driven Web Components UI framework with interchangeable design dialects for modern web products.

Readme

Crusher UI Kit

A token-driven, framework-agnostic Web Components design system. 53 public custom elements, 8 visual dialects, light/dark, density, runtime + static delivery. Built with Lit. Zero framework lock-in.

npm i crusher-ui-kit

Live demo: ui.muhammadhassaanjaved.com Current release: 1.5.0


Why

You're shipping multiple surfaces, a marketing site, a portfolio, an admin tool, an AI workspace. They each want a slightly different feel but the same system underneath. Crusher gives you one token pipeline, one component library, and seven visual dialects (Glass, Brutal, NeoBrutal, Neumorph, Minimal, Futuristic, Bento) that you switch with a single root-attribute.

The components are Web Components, so they work in React, Vue, Astro, vanilla HTML, or no framework at all.


Install paths

1. Bundler app (Vite / Next / Astro / etc.)

// Register all custom elements + import the core token CSS.
import 'crusher-ui-kit';
import 'crusher-ui-kit/styles/tokens.css';
import 'crusher-ui-kit/styles/modes.css';
import 'crusher-ui-kit/styles/semantic.css';
import 'crusher-ui-kit/styles/bridge.css';
import 'crusher-ui-kit/themes/glass.css'; // pick one or more dialect overlays

import {
  setTheme, setMode, setDensity, setBrand,
  showToast, openPalette, createHashPanelController,
} from 'crusher-ui-kit/runtime';

Theme imports are extension-qualified: import 'crusher-ui-kit/themes/<name>.css'. Pick only the dialects you ship.

2. Static HTML (no bundler)

<html data-theme="minimal" data-mode="dark">
<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/crusher-ui.min.css" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/themes/minimal.css" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/static/tool-shell.css" />
  <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/static/tool-shell.js"></script>
  <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/crusher-ui.standalone.esm.js"></script>
</head>
<body>
  <crusher-button>Hello</crusher-button>
</body>
</html>

For utility-tool sites, start from templates/static-tool-shell.html. The tool-shell.js IIFE hydrates <html data-default-*> attributes from saved prefs before paint, so themed sites don't flash.

3. Astro / SSR

Same as a bundler app, register on the client. Importing crusher-ui-kit from Node (Next.js App Router server render, Astro SSR, vitest in a node environment) is a safe no-op: the root entry resolves to an inert stub on the server, and element registration happens in the browser via the browser / import conditions. No dynamic-import workaround is needed. The recommended pattern for Astro is in hassaan-site and crusher-portfolio: a small scripts/resolve-crusher-ui.mjs runs on predev/prebuild that links a sibling framework checkout for local work and installs the pinned published version for hosted builds.


Concepts

Theme (dialect), the aesthetic. Set on the root: <html data-theme="glass">. Seven dialects ship today. Each overrides design tokens (radii, shadows, transitions, type) on top of the base palette.

Mode, light vs dark. Set on the root: <html data-mode="dark">. Independent of theme; every dialect supports both modes.

Density, comfortable (default) or compact. Set on the root: <html data-density="compact">. Tightens spacing tokens for data-heavy surfaces.

Brand color, a single tokenized accent (--crusher-color-brand-primary). Override via CSS or via setBrand('#7c3aed'). New in 0.3.0: --crusher-brand-on-surface is auto-derived to soften brand against the dark canvas via color-mix.

Theme lock, pin a fixed dialect for product sites: <html data-theme="minimal" data-theme-lock="minimal">. Lock wins in memory only and does not write back to localStorage, so the user's saved brand color is preserved.


Component catalog (53 public elements)

Atoms (11)

| Tag | Notes | | --- | --- | | <crusher-badge> | Inline label / count | | <crusher-button> | Variants: primary (default), outline, ghost, link, destructive / danger alias. Sizes: sm / md / lg. loading boolean. Respects prefers-reduced-motion. | | <crusher-checkbox> | Accessible checkbox | | <crusher-chip> | Token-styled chip / tag. Supports color attribute. | | <crusher-code-block> | Code snippet with token-aware syntax highlighting | | <crusher-currency-input> | Monetary amount entry. Locale-formats on blur (1234.5 becomes 1,234.50), shows the raw number while focused. currency (ISO 4217), locale (BCP-47), min/max/step, prefix-symbol. Exposes numericValue. | | <crusher-icon-link> | Anchor styled as an icon button | | <crusher-input> | Text input, validates, ARIA-wired | | <crusher-switch> | Toggle switch | | <crusher-textarea> | Multi-line input | | <crusher-tooltip> | Hover/focus tooltip |

Molecules (30)

| Tag | Notes | | --- | --- | | <crusher-alert> | Variants: info / success / warning / danger / neutral. Dismissible with persistence by id. | | <crusher-avatar-menu> | Avatar-anchored profile dropdown for app shells. name (drives initials + accessible label), email, avatar-url, placement, open. Slots: default menu items, footer for a pinned sign-out. Escape closes and returns focus. | | <crusher-audio-player> | Ambient audio control. preload="none" by default (set preload="metadata"/"auto" to override). Album art deferred until first play. | | <crusher-banner> | Site-wide announcement strip. Variants: brand / info / success / warning / danger. Dismissible with separate-map persistence. | | <crusher-card> | Surface container. Variants: default / data / highlight / muted. Density-aware. hover opts into hover-lift. | | <crusher-card-link> | Whole-card link wrapper for <crusher-card> | | <crusher-chart> | Dashboard-grade SVG chart with axes, gridlines and labels: the step up from <crusher-sparkline> and the step below a chart library. type bar / line, optional area fill. For tooltips, zoom, brushing or 3+ series, reach for Recharts or ECharts. | | <crusher-command-palette> | ⌘K-style palette. Async loaders supported. | | <crusher-connection-status> | State of an external connection (OAuth, DB, webhook). provider, state (connected / disconnected / reauth-required / error / connecting), since, error-message, compact. cta slot for the reconnect action; the component fires no events of its own. | | <crusher-disclosure> | Lit wrapper over native <details> with two-way open reflection. Also available as the .crusher-disclosure CSS-only utility class for no-build consumers. | | <crusher-dropdown> | Menu with keyboard navigation | | <crusher-empty-state> | No-results / empty surface. Slots for icon and actions. | | <crusher-file-upload> | Drag-drop zone with file-list preview and client-side validation (accept, max-size, max-files, multiple). Manages selection and UX only: the upload itself stays yours. Emits files-change and file-remove; exposes files and clear(). | | <crusher-filter-bar> | Wires data-value chips to filter a target selector by attribute | | <crusher-grid> | Responsive auto-fit grid primitive. min="13rem" controls column width. | | <crusher-invoice-list> | Billing-page table of past invoices over <crusher-table>, adding locale-aware dates, currency-aware amounts and provider-agnostic status badges (Polar / Stripe / Paddle conventions). amount-in-cents for minor units, page-size (0 disables pagination). Emits invoice-download. | | <crusher-kpi> | The dashboard staple: big value, label, delta with up/down tone, optional trend. Composes <crusher-sparkline> rather than re-implementing it. invert for metrics where down is good. | | <crusher-menubar> | Horizontal menu / toolbar | | <crusher-modal> | Focus-trap modal. close-on-overlay opt-in. Emits cancelable request-close. | | <crusher-payment-method-card> | A saved payment method: brand, last4, expiry, optional default / expired badges, plus an empty state. Display-only, since card collection belongs at the provider's hosted portal. cta slot links there. | | <crusher-plan-card> | Pricing tier. plan-name, price, description, featured (elevated recommended tier), current (adds a "Current plan" badge). Slots for the feature list, cta and a badge. Lift respects prefers-reduced-motion. | | <crusher-select> | Async + type-ahead select, ARIA-announced | | <crusher-skill-bar> | Animated progress bar | | <crusher-sparkline> | Inline trend chart: line, area, dots, no axes or tooltips. Auto-derives an aria-label summarising min/max/last/delta. Deliberately not a chart library; <crusher-chart> is the next step up. | | <crusher-stack> | Flex layout primitive. gap, align, justify, orientation (vertical default / horizontal), wrap. | | <crusher-stat> | Data metric tile. tone, size (sm/md/lg/xl), orientation. Slots for icon + trend. | | <crusher-style-switcher> | Theme/mode/brand control. Hide-* boolean attrs scope each section. inline boolean renders in-flow (new in 0.3.1). Use swatches attribute for consumer-defined palettes. | | <crusher-tabs> | ARIA tablist with roving focus | | <crusher-toast-center> | Global toast region. Drive via showToast(...) from crusher-ui-kit/runtime. | | <crusher-toolbar> | Section toolbar, leading / center / actions slots |

Organisms (7)

| Tag | Notes | | --- | --- | | <crusher-app-shell> | Page-level shell. Slots: nav, aside, footer, default. Composes with <crusher-page-header> + <crusher-grid> + <crusher-stack>. | | <crusher-nav-list> | Sidebar / drawer nav list | | <crusher-page-header> | Page-level header with kicker, title, lede, meta, pills | | <crusher-section-title> | Section heading with under-bar treatment | | <crusher-table> | Data table with token-driven density | | <crusher-timeline> | Vertical / branching timeline container | | <crusher-timeline-item> | Individual timeline event |

Forms (4)

| Tag | Notes | | --- | --- | | <crusher-field> | Wrapper that auto-wires aria-labelledby/aria-describedby/aria-invalid on the [slot="control"] element. label / helper / error attrs or slots. orientation vertical / horizontal. | | <crusher-label> | Standalone label primitive | | <crusher-hint> | Helper text primitive | | <crusher-error> | Error text primitive |

Plus <crusher-scene>, the declarative form of the Environment Scenes runtime, documented in its own section below. It registers from the runtime rather than from src/components, which is why it is not in a table here.

A new public element must appear in index.html (check:demo) and types/index.d.ts (check:types), both of which run in CI, and it picks up a crusher-ui-kit/components/<tag> entry plus a generated .d.ts automatically.


Runtime helpers

import {
  setTheme, lockTheme, setMode, setDensity, setBrand, resetBrand, getBrand,
  showToast, openPalette, createHashPanelController,
} from 'crusher-ui-kit/runtime';

setTheme('glass');
lockTheme('minimal');        // pin a fixed dialect (in-memory only, no localStorage write)
setMode('dark');
setDensity('compact');
setBrand('#7c3aed');         // persists to crusher_prefs; sanitization allowlist applies
resetBrand();                // back to the active theme primary
getBrand();                  // current effective brand color

showToast({ title: 'Saved', variant: 'success' });
openPalette();

createHashPanelController({
  defaultId: 'home',
  panelSelector: '[data-crusher-panel]',
  linkSelector: '[data-crusher-panel-link]',
  inactiveClass: 'is-hidden',
});

All preferences persist to localStorage under crusher_prefs (schema version v: 1). Future versions up-migrate on read; unknown future versions become read-only with a one-time console.warn.


Environment Scenes

Opt-in living atmosphere. Two slots, deliberately: a scene is a place and owns the palette; an atmosphere is a stack of conditions that compose, each at its own intensity, and owns the particles and the colour wash. It can rain in a forest, and blossom can still drift through a dust storm.

import {
  setScene, setAtmosphere, getAtmosphere,
  listScenes, listAtmospheres, sampleVeil, MAX_PARTICLES,
} from 'crusher-ui-kit/runtime';
import 'crusher-ui-kit/styles/scenes.css';

setScene('forest');                       // place + the atmosphere it opens with
setScene('forest', { atmosphere: false }); // palette only; you drive the weather

setAtmosphere([                            // layers compose
  { name: 'petals', intensity: 0.3 },      // the season's own air, thinned
  { name: 'dust', intensity: 1 },          // the weather, drowning it out
]);
setAtmosphere('rain:0.8 leaves:0.4');      // string form
setAtmosphere('rain', { veil: false });    // particles only, you own the wash
setAtmosphere([]);                          // clear

listAtmospheres({ kind: 'weather' });      // 23 conditions, for a picker
listAtmospheres({ kind: 'ambient' });      // 6 kinds of ambient air

Declaratively:

<crusher-scene name="forest" atmosphere="rain:0.8 leaves:0.4"></crusher-scene>

Intensity reaches 0. A clear day means no rain at all, not a light drizzle: a layer at intensity 0 is absent, not floored.

One particle budget across the whole stack, MAX_PARTICLES = 180, scaled proportionally when a stack asks for more. Measured, not guessed: this renderer holds 60fps to about 200 animated elements on integrated graphics and degrades past ~230. If you need 500 particles you want a canvas, which this is not.

prefers-reduced-motion drops the motion live, including mid-session, and keeps the colour wash, which carries no motion.

Proving your text survives the wash

The veil darkens the page margins and leaves the reading zone clear, and every condition ships a mode-anchored colour you can override with --crusher-atmos-veil-top / --crusher-atmos-veil-edge. Because it is edge-weighted, "is my text still legible" depends on where the text sits, so the kit ships the measurement rather than a silent fix:

import { composite, contrastRatio, fitOverlays } from 'crusher-ui-kit/runtime/contrast';

const ground = composite(myPaper, sampleVeil('thunderstorm', 0.5, 0.05));
contrastRatio(myInk, ground);                                   // what the reader sees
fitOverlays(myPaper, sampleVeil('thunderstorm', 0.5, 0.05),      // the strongest the veil
  [[myInk, 4.5], [myMutedInk, 3]]);                              // can be and stay legible

Nothing here runs on its own and nothing rewrites your tokens; applying a result is your call. The kit holds its own defaults to the same standard in CI via npm run check:atmosphere.

Design rationale and the measurements behind every number above: ADR 0003.


Accessibility

  • Modal traps focus, restores on close, locks body scroll, dismisses on Esc + overlay click, emits cancelable request-close.
  • Tabs follow the WAI-ARIA tablist pattern: role="tablist" / tab / tabpanel, roving focus, live announcements.
  • Select announces selections via aria-live, async data + type-ahead.
  • Field auto-generates stable IDs and wires aria-labelledby / aria-describedby / aria-invalid on the slotted control.
  • Style switcher has :focus-visible outlines, aria-pressed / aria-expanded, respects prefers-reduced-motion.
  • Contrast validated via npm run check:contrast (WCAG AA contrast on every theme/mode pair).
  • Reduced motion honored in spinners, transitions, hover lifts, and modal animations.

Browser support

  • Modern evergreen browsers (Chrome / Edge / Safari / Firefox last two versions).
  • color-mix(in srgb, ...) is used widely, requires Chrome 111+, Safari iOS 16.2+, Firefox 113+.
  • interpolate-size: allow-keywords for <crusher-disclosure> animation requires Chrome 129+ / Safari 18+; gracefully degrades to instant open/close elsewhere.
  • Node 20.19+ (or 22+) for the dev / build toolchain, as engines requires. The published package itself runs wherever your bundler and browsers do.

Migration notes

From ^0.2.x to ^0.3.x

No breaking changes. Additive only:

  • New: <crusher-field>, <crusher-disclosure>, <crusher-stack orientation="horizontal" wrap>, <crusher-button variant="danger"> alias, --crusher-brand-on-surface per-mode token.
  • New (0.3.1): <crusher-style-switcher inline> for in-flow rendering, <crusher-audio-player preload="..."> attribute (default flipped to "none").
  • Element count: 41 → 42.

If you used <crusher-audio-player> and relied on the browser default preloading the source on page load, set preload="metadata" or "auto" explicitly to restore that behavior.

From ^0.1.x to ^0.2.x

  • data-theme-lock now wins in memory only, it does not write the locked theme back to localStorage. Smoke checks in hassaan-site / crusher-portfolio were updated to assert this contract.
  • crusher_prefs schema versioned (v: 1). Pre-0.1.6 prefs up-migrate automatically.
  • Element count: 39 → 41 (M3 added crusher-alert and crusher-banner).

Scripts

| Command | Purpose | | --- | --- | | npm run dev | Vite dev server on the framework demo site | | npm run build:tokens | Generate token + theme CSS from JSON sources | | npm run build | Build distributable bundles (ESM + standalone ESM + CSS + static assets) | | npm run check:contrast | WCAG color-contrast validation across every theme/mode pair | | npm run check:package | Verify runtime exports + package contract | | npm run check:browser | Playwright-driven browser contract assertions | | npm run check:demo | Every public custom element renders in index.html | | npm run check:types | Every public custom element typed in types/index.d.ts |

Release gate (run before tagging):

npm run build:tokens && npm run check:contrast && npm run build && \
  npm run check:package && npm run check:browser && \
  npm run check:demo && npm run check:types && npm pack --dry-run

Releases are Changesets-driven via GitHub Actions. The publish command uses npm publish --no-provenance --access public (the GitHub repo is private; the npm package is public). NPM_TOKEN is configured as a GitHub Actions secret.


Project structure

crusher-ui-kit/
├── design/tokens/          ← base token JSON (source of truth)
├── design/themes/          ← per-dialect token overlays (JSON)
├── src/
│   ├── components/         ← Lit Web Components (atoms / molecules / organisms / forms)
│   ├── css/                ← generated tokens.css, modes.css, themes/*.css (do not hand-edit)
│   ├── runtime/            ← setTheme, setMode, setDensity, setBrand, prefs, panels, toast
│   ├── static/             ← tool-shell.css + tool-shell.js (no-build CDN consumers)
│   ├── scripts/            ← framework demo site scripts
│   └── js/main.js          ← bundler entry; registers every public element
├── templates/              ← canonical static-tool-shell.html
├── scripts/                ← build / contract checks / token regeneration
├── types/index.d.ts        ← TS / VS Code IntelliSense for `<crusher-*>` tags
└── docs/                   ← internal architecture + roadmap (not published)

Public package contract is defined by the exports field in package.json. Anything outside it is internal and may change.


TypeScript

types/index.d.ts declares every public custom element in the global HTMLElementTagNameMap. With moduleResolution: "NodeNext" or "Bundler", document.createElement('crusher-button') returns the correctly typed instance. Bring-your-own framework typings (Vue / Svelte / Solid) can extend the same map.

React / TSX

crusher-ui-kit/react ships a JSX.IntrinsicElements augmentation covering every public element, so <crusher-button> type-checks in TSX (React 19 module JSX and the older global JSX namespace both covered). Opt in from a .d.ts in your project (for example env.d.ts):

/// <reference types="crusher-ui-kit/react" />

Requires React typings (@types/react or React 19's bundled types). Coverage is gated by check:types, so a new element cannot ship without a JSX entry.


Tailwind CSS v4

crusher-ui-kit/tailwind.css is a generated Tailwind v4 theme preset (@theme inline) exposing the design tokens as crusher--namespaced utilities, so nothing needs hand-mapping:

@import "tailwindcss";
@import "crusher-ui-kit/tailwind.css";
<div class="bg-crusher-canvas text-crusher-text-primary rounded-crusher-lg shadow-crusher-1 p-crusher-4">
  <span class="text-crusher-brand-primary font-crusher-display">Tokens, no hand-mapping</span>
</div>

Every entry references the live --crusher-* custom property, so setTheme / setMode / setBrand keep working inside Tailwind utilities. The file is generated from the token build (npm run build:tokens via scripts/build-tailwind-theme.mjs) and cannot drift from the tokens; it also imports tokens.css, modes.css and semantic.css, so it works standalone. Also served on the CDN at dist/styles/tailwind.css.


Changelog

See CHANGELOG.md for per-release notes generated by Changesets.


License

MIT. Built and maintained by Hassaan (itxcrusher). muhammadhassaanjaved.com