crusher-ui-kit
v1.0.0
Published
Token-driven Web Components UI framework with interchangeable design dialects for modern web products.
Maintainers
Readme
Crusher UI Kit
A token-driven, framework-agnostic Web Components design system. 42 public custom elements, 7 visual dialects, light/dark, density, runtime + static delivery. Built with Lit. Zero framework lock-in.
npm i crusher-ui-kitLive demo: ui.muhammadhassaanjaved.com
Current release: 0.3.1
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. 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 (42 public elements)
Atoms (10)
| 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-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 (20)
| Tag | Notes |
| --- | --- |
| <crusher-alert> | Variants: info / success / warning / danger / neutral. Dismissible with persistence by id. |
| <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-command-palette> | ⌘K-style palette. Async loaders supported. |
| <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-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-menubar> | Horizontal menu / toolbar |
| <crusher-modal> | Focus-trap modal. close-on-overlay opt-in. Emits cancelable request-close. |
| <crusher-select> | Async + type-ahead select, ARIA-announced |
| <crusher-skill-bar> | Animated progress bar |
| <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 |
A new public element must appear in index.html (check:demo) and types/index.d.ts (check:types) — both gates run in CI.
Runtime helpers
import {
setTheme, lockTheme, setMode, setDensity, setBrand,
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');
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.
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-invalidon the slotted control. - Style switcher has
:focus-visibleoutlines,aria-pressed/aria-expanded, respectsprefers-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-keywordsfor<crusher-disclosure>animation requires Chrome 129+ / Safari 18+; gracefully degrades to instant open/close elsewhere.- Node 18+ for
dev/buildtoolchain.
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-surfaceper-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-locknow wins in memory only — it does not write the locked theme back tolocalStorage. Smoke checks inhassaan-site/crusher-portfoliowere updated to assert this contract.crusher_prefsschema versioned (v: 1). Pre-0.1.6prefs up-migrate automatically.- Element count: 39 → 41 (M3 added
crusher-alertandcrusher-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-runReleases 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.
Changelog
See CHANGELOG.md for per-release notes generated by Changesets.
License
MIT. Built and maintained by Hassaan (itxcrusher). muhammadhassaanjaved.com
