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

@chomuiro/saisei

v4.4.0

Published

Blueprint drafting-style reusable frontend kit (tokens + components + motion + React library). No build step, no framework lock-in.

Readme

Saisei — reusable frontend kit (Blueprint design language)

A drafting/schematic design language, not a generic SaaS kit. Three files, no build step, no framework lock-in.

  • tokens.css — every color, font, spacing, and radius value, as CSS variables. This is the only file you should need to touch when starting a new project.
  • components.css — buttons, forms, cards, badges, alerts, title-block nav, tabs, accordion, modal, toast, table, avatar, skeleton loaders. Reads only from tokens.css.
  • components.js — vanilla JS that wires up modals, tabs, accordions, and toasts via data-* attributes. No dependencies, no build step.

Open index.html to see every component and copy markup straight from it.

Reuse in any website

Two files carry the whole kit. Pick one path:

A — Copy files (simplest, no npm). Copy dist/blueprint.css + dist/blueprint.js (build with npm run build, or use tokens.css + components.css + components.js directly), then:

<link rel="stylesheet" href="blueprint.css">
...
<script src="blueprint.js"></script>

Copy any markup block from index.html — classes just work. See examples/plain-html.html.

B — npm. npm i @chomuiro/saisei, then import once at your app root:

import "@chomuiro/saisei/dist/blueprint.css"; // or ./css export
import "@chomuiro/saisei/dist/blueprint.js";  // optional: data-* auto-wiring

C — CDN. After publishing, pin a version from unpkg/jsDelivr:

<link rel="stylesheet" href="https://unpkg.com/@chomuiro/[email protected]/dist/blueprint.css">
<script src="https://unpkg.com/@chomuiro/[email protected]/dist/blueprint.js"></script>

Frameworks: keep the CSS classes, re-implement open/close state in your framework for production (quick prototypes can keep blueprint.js as-is — it queries the DOM directly).

React library (@chomuiro/saisei/react, peer dep react — verified with esbuild):

import "@chomuiro/saisei/dist/blueprint.css";
import { Button, Card, Modal, Tabs, ToastProvider, useToast, TickerTape, ThemeToggle } from "@chomuiro/saisei/react";

Components: Button (primary/secondary/ghost/danger), Card/Badge/Alert/Skeleton, Empty/Progress/Crumbs, controlled Modal (Escape, focus trap, scroll lock), Tabs (arrow-key ARIA pattern), Accordion, ToastProvider + useToast(), TickerTape, ThemeToggle, DataTable (columns + rows, sort/filter/pager built in), CommandPalette (items + open/onOpenChange, Ctrl/⌘+K wiring is yours), Dropdown (label + items), Combobox (label + options), Drawer (side panel, same contract as Modal), Modal accepts size="sm"|"lg", ToastProvider actions via toast(msg, { label, run }), Reveal (scroll entrance + delay stagger), AnimatedNumber (eased counts). Typed via react/index.d.ts (npm run test:types). Full example in examples/react.jsx. Vue helper: adapters/vue.js + examples/vue.vue. Svelte: adapters/svelte.js (theme store, initTheme, toggle) + examples/svelte.svelte.

DataTable (vanilla): wrap any table — sorting is numeric-aware, filtering matches whole-row text, only the current page stays in the DOM:

<div data-datatable data-page-size="3">
  <input data-table-filter placeholder="Filter…" aria-label="Filter table">
  <div class="table-wrap"><table class="table">
    <thead><tr><th scope="col" data-sort="name">Name</th>…</tr></thead>
    <tbody>…</tbody>
  </table></div>
  <button data-table-prev>← Prev</button>
  <span data-table-info></span>
  <button data-table-next>Next →</button>
</div>

Command palette (vanilla): Ctrl/⌘+K or any [data-palette-open] button opens #palette; it auto-collects nav anchor links plus [data-command] elements (↑↓/Enter/Esc, listbox pattern). Copy the #palette block from index.html.

Scroll reveals: add .reveal-on-scroll to any section — it animates in on first scroll into view (same rise as .reveal, same duration tier); React trees use <Reveal delay>.

Motion details: React Modal animates closed (stays mounted 240ms), Tabs slides a measured indicator, Accordion fits content height, CommandPalette mounts with palette-in, Progress sweeps from 0; vanilla count-ups via [data-countup="1234" data-decimals data-prefix data-suffix data-duration].

Dropdown / tooltip / combobox (vanilla): trigger <button data-menu-trigger aria-expanded="false" aria-controls="menu-id"> with <ul class="menu" id="menu-id" role="menu" hidden> of .menu-items (click/Esc/ outside-click/arrows handled); CSS-only tooltip via data-tip (avoid on .btn-primary — both use ::after); combobox via .combo-wrap + input[role="combobox"] + ul.combo-list[role="listbox"] of li[role="option"][data-value].

Empty / progress / crumbs: .empty (dashed panel + .empty-title), .progress[role="progressbar"] with <i style="--value: 65%;">, nav > ol.crumbs with aria-current="page" on the last item.

Token playground: open playground.html — tweak surfaces, radius, and font stacks against a live component preview, then Copy CSS exports only the changed tokens as a :root (or [data-theme="dark"]) override block. One-click presets (Blueprint / Warm / High-contrast / Soft, same values as themes.json; dist/theme-<name>.css is built for direct inclusion). Design tools consume tokens.json (Style Dictionary format, verified against tokens.css in CI).

Scaffold: npx @chomuiro/saisei init [dir] (bin saisei, zero deps) copies the dist bundle + starter page; never overwrites.

Drawer / sizes / toast actions: [data-drawer-open] + .drawer-overlay side panel (focus trap, Esc, scroll lock, RTL-mirrored); .modal-sm / .modal-lg; toast(msg, { label, run }) in vanilla and React.

Direction & print: layout uses logical properties throughout (verified with an RTL axe run); @media print hides chrome and keeps panels whole; @media (forced-colors: active) maps boundaries and focus to system colors.

Releasing: push a v* tag — .github/workflows/release.yml runs the full gate + e2e and npm publishes (needs a repo NPM_TOKEN secret). Version bumps ride with a CHANGELOG.md entry, same commit.

Production files: dist/blueprint.min.css/js (esbuild-minified) with SRI hashes in dist/integrity.json for your <link>/<script integrity>. React ships react/index.d.ts (adapters typed too); importing components.js is SSR-safe (no DOM access at import time).

Motion pack (in components.css, all token durations, monochrome, reduced-motion safe): .ticker-tape infinite strip (pauses on hover/focus — see react/TickerTape.jsx), .draw-in sketch-on registration corners, .btn-primary hover sheen, .card-enter entrance, .pulsing loader. One animated element per viewport is plenty.

The signature moves

Three things carry the identity — reuse them, but sparingly, the way the demo does:

  • .reg-corners — registration-mark ticks in the accent color, on major surfaces only (cards, the hero title block, the modal). Don't put it on every element or it stops meaning anything.
  • .sheet-label — the small mono "SHEET 01 — ..." tag before a section heading.
  • .dim-divider — the dimension-line section break, used instead of a plain <hr>. Markup:
    <div class="dim-divider"><div class="dim-divider-line"></div><span class="dim-divider-label">Sheet 02</span><div class="dim-divider-line"></div></div>

Starting a new plain HTML/CSS site

Copy all three files into the project, then in your <head>:

<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="components.css">
...
<script src="components.js"></script>

Copy whatever markup blocks you need from index.html.

Re-skinning for a new brand

Open tokens.css and change the values under :root. A few starting points:

  • --accent / --accent-hover / --accent-tint — the primary action color (pure black in light mode, pure white in dark). --accent-mid and --accent-deep are the grays that carry warning/danger weight.
  • --paper / --paper-alt — page background vs. card/input background.
  • --font-display / --font-ui / --font-mono — swap in your own typefaces (update the Google Fonts <link> in index.html to match). Keep a monospace assigned to --font-mono even if you drop the drafting theme entirely — several components (labels, badges, table) depend on it for the technical read.
  • --radius-sm/md/lg/xl — currently all 2px (sharp corners is this kit's signature choice). Raise them if you want a softer, more conventional feel — but that's the one thing that will make it look like every other kit again, so change it deliberately.

Everything else follows automatically — no component CSS needs editing.

Using it inside React, Vue, or Svelte

The CSS has no framework dependency, so it works as your base styling layer:

  1. Copy tokens.css and components.css into src/styles/ and import both once at your app root (e.g. in main.jsx or App.vue).
  2. Use the class names directly in JSX/templates: <button className="btn btn-primary">Save</button>.
  3. For the interactive components (modal, tabs, accordion), you have two options:
    • Keep using components.js as-is for quick prototypes — it queries the DOM directly and works fine alongside React.
    • For a production React/Vue app, re-implement the open/close state in your framework's state system (e.g. useState for modal visibility) and keep only the CSS classes from this kit. The class names and structure in index.html are the reference markup to follow.

Monochrome, with a dynamic light/dark toggle

No hue anywhere in this kit — every value in tokens.css is black, white, or a shade of gray. Toggle between them by setting an attribute on <html>:

document.documentElement.dataset.theme = "dark"; // or "" to go back to light

The dark values live in tokens.css under [data-theme="dark"] — same shape as the light palette, just inverted.

How status still reads without color: warning and danger don't use amber/red — they use the line-type conventions real technical drawings use to distinguish edge types. Solid border = neutral/active, dashed border = warning/pending, double border = danger. Look at .badge-warning, .badge-danger, .alert-warning, .alert-danger in components.css — that pattern is the thing to reuse if you add new states, rather than reaching for a color.

Motion — one scale, not scattered numbers

Every transition and animation in components.css draws from three named durations in tokens.css, not ad hoc millisecond values:

  • --duration-fast (160ms) — hover/press feedback (buttons, inputs, switches)
  • --duration-base (240ms) — content changes (tab panels, accordion, modal backdrop)
  • --duration-slow (320ms) — modal panel transform, theme cross-fade, page-load entrance

If you add a new animated component, pick one of these three rather than inventing a fourth. The .reveal class (staggered fade-up on page load, see the sections in index.html) and the sliding .tab-indicator both use this same scale.

Respected everywhere via prefers-reduced-motion — anyone with that OS setting gets every transition/animation collapsed to near-zero automatically.

Accessibility this kit actually implements

  • Modal has a real focus trap — Tab/Shift+Tab cycle within the dialog only; focus can't leak to the page behind it. Body scroll is locked while open and restored on close, and focus returns to whatever triggered the modal.
  • Tabs are keyboard-operable, per the ARIA tablist pattern — Left/Right move focus and selection together, Home/End jump to the ends. components.js wires this automatically for any [data-tabs] block.
  • Skip link — first Tab press on the page reveals a "Skip to content" link that jumps past the nav, and the page content sits inside a proper <main> landmark with the nav links in <nav>.
  • Toasts announce themselves via aria-live="polite" for screen readers.
  • Focus rings use :focus-visible (keyboard only, no mouse-click ring noise) and read from --focus-ring in tokens.css.

Audited with axe-core against light mode, dark mode, and modal-open — zero violations across all three at the time of writing. That's automated coverage (landmarks, ARIA correctness, contrast, name/role/value), not a substitute for testing with an actual screen reader — worth doing before shipping this in a real product. Cross-browser testing was limited to Chromium in this environment (no network access to fetch other browser binaries here); Firefox/WebKit/Safari should be checked separately, especially the max-height accordion animation and the backdrop/focus-trap behavior in Safari specifically, which has historically been pickier about both.

Theme persistence

The toggle remembers your choice via localStorage, and falls back to the OS-level prefers-color-scheme on a first visit with nothing stored. A small blocking script in index.html's <head> (not components.js, which loads too late) sets the theme attribute before first paint, so there's no flash of the wrong theme on load or refresh.

Production fixes applied

  • Modal/tab hidden state — .modal-overlay and .tab-panel[aria-hidden="true"] now use visibility: hidden (not just opacity), plus inert + aria-hidden toggling in components.js. Closed dialogs and inactive panels are out of the tab order and the a11y tree.
  • Form errors — demo email field now has aria-invalid="true" + aria-describedby="email-error" with role="alert" on the message. Copy this pattern for new validation.
  • Tables — the demo table sits in .table-wrap (overflow-x: auto, tabindex="0", role="region") so it scrolls on small screens. Copy this wrapper for every table you add.
  • Modal on mobile — .modal has max-height: min(88vh, 640px) + overflow-y: auto; 92vh under 480px.
  • Accessibility status — real axe-core run in CI (tests/e2e/axe.spec.js via Playwright Chromium): zero violations across light, dark, modal-open, palette-open, and playground. The engine caught two real issues during development (duplicate unnamed accordion landmarks → now aria-labelledby; --ink-soft at 4.18:1 → darkened to #5F5F5F for 4.5:1 on both papers). Still do a screen-reader pass before shipping, and check Firefox/Safari separately.

Buttons (primary/secondary/ghost/danger, sizes, disabled) · text inputs, select, textarea, checkbox, switch, validation states · dropdown menu · CSS-only tooltip · combobox · spec-sheet cards with registration corners · stamped badges · annotation-style alerts · empty states · progress · breadcrumbs · title-block hero/nav · drawing-sheet-index tabs · accordion · pop-up modal · toast notifications · schematic data table (sortable/filterable/paged) · command palette (Ctrl/⌘+K) · scroll reveals · avatar · skeleton loaders.

Add new components by following the same pattern: style with tokens only, and if it needs interactivity, add a small block to components.js using the data-* attribute convention already in use.