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

swatchkit

v5.4.0

Published

A lightweight tool for creating HTML pattern libraries.

Readme

SwatchKit

SwatchKit is a lightweight tool for generating HTML pattern libraries and design systems. It scans your folders for components, stitches them into a documentation site, and lets you share the same render functions between your app and the library.

The "Magic Folder" principle: drop files in, a library comes out.

The pattern: one source of truth

The interesting case is when your app and your pattern library share render functions. You write renderButton() once, use it in src/pages/home.js for the real app, and use it again in swatchkit/swatches/button/index.js for the docs. Edit one, both update.

my-project/
├── src/
│   ├── components/button.js          # shared renderButton
│   ├── pages/home.js                 # calls renderButton → real app HTML
│   └── css/                          # cssDir — your stylesheet source
│       └── global/tokens.css         # design tokens (CSS, with @swatchkit blocks)
├── swatchkit/
│   ├── _swatchkit.html
│   ├── _preview.html
│   └── swatches/
│       └── button/
│           ├── index.js              # calls renderButton → swatch HTML
│           └── demo.js               # sibling asset, copied to dist
├── scripts/                          # build scripts (from `init --app`)
│   ├── build-site.js                 # renders src/pages/home.js → dist/index.html
│   └── build-assets.js               # esbuild bundles CSS + JS
├── swatchkit.config.js
└── dist/                             # generated
    ├── index.html                    # main app (rendered at build time)
    ├── css/                          # one copy, shared
    └── swatchkit/
        ├── index.html                # references ../css/main.css
        └── preview/...

Quick start

# 1. Create config + scaffold CSS blueprints, layout templates, starter tokens
#    (picks CJS or ESM config based on package.json#type)
npx swatchkit init --cssDir ./src/css

# 2. Build
npx swatchkit

swatchkit init prompts for cssDir (default ./src/css) when --cssDir is omitted, writes swatchkit.config.js, and scaffolds the project in one step. If you run swatchkit with no config file, the build falls back to ./css. The build output goes to dist/swatchkit/ by default.

Full app in one command

To scaffold a complete integrated app — esbuild build scripts, shared renderButton/renderCard functions, a home page, two example swatches, and a watch-enabled package.json — add --app:

mkdir my-app && cd my-app

# 1. Create package.json and make the project ESM FIRST (see note below).
npm init -y
npm pkg set type=module private=true

# 2. Install tooling, then scaffold + run.
npm install -D swatchkit esbuild
npx swatchkit init --app --cssDir ./src/css
npm install
npm run dev      # builds, watches, serves at http://localhost:8080

The app is at /, the pattern library at /swatchkit/, both sharing one bundled CSS file. See the hand-rolled app setup guide.

Set "type": "module" before running init --app. The app starter is ESM, so it writes an ESM swatchkit.config.js (export default { … }) and ESM build scripts. If your package.json isn't "type": "module", Node loads the config as CommonJS and the build fails with:

Warning: Failed to load the ES module: …/swatchkit.config.js.
Make sure to set "type": "module" in the nearest package.json …
[SwatchKit] Error: Unexpected token 'export'

Fix it by setting ESM and re-running (use --force to refresh the config):

npm pkg set type=module
npx swatchkit init --app --cssDir ./src/css --force

The plain swatchkit init (without --app) doesn't need this — it picks CJS or ESM config syntax based on your existing package.json#type.

For real projects, install as a dev dep and add a script:

npm install -D swatchkit
"scripts": {
  "patterns": "swatchkit"
}

The Magic Folder

SwatchKit reads from a single folder (swatchkit/ by default). Top-level folders under swatchkit/ become sidebar sections. Folders directly inside those sections become swatches.

swatchkit/
├── tokens/                # section: "Design Tokens" (generated — see below)
│   ├── colors.html
│   └── text-sizes.html
├── swatches/              # section: "Swatches"
│   ├── button/
│   │   └── index.html
│   └── card/
│       ├── index.html
│       ├── styles.css     # sibling assets copied alongside
│       └── script.js
├── compositions/          # section: "Compositions"
│   └── sidebar/index.html
└── utilities/             # section: "Utilities"
    └── flow/index.html

Rules:

  • One level deep. A swatch is a folder with an index.html (or index.js, see below). Nested groups like swatches/components/button/ are not scanned — use swatches/button/.
  • Underscore prefix is ignored. _wip/, _notes.md, _swatchkit.html all stay out of the build.
  • Sibling assets travel with the swatch. Anything next to index.html (CSS, JS, images) is copied to the build output, so your swatch can reference them with relative paths.
  • swatchkit/tokens/*.html is generated, not hand-written. SwatchKit regenerates it on every build from your @swatchkit token blocks (see Design tokens). Don't edit those files.

JavaScript swatches

A swatch folder may use index.js instead of index.html to generate HTML programmatically. This is how you share render functions between your app and the library.

// swatchkit/swatches/button/index.js
import { renderButton } from "../../../src/components/button.js";

const html = String.raw;

export default html`
  <h2>Button</h2>
  ${renderButton({ label: "Save changes" })}
  ${renderButton({ label: "View brand", href: "/brands/aurora/" })}
`;
  • index.js runs at build time and must default-export an HTML string. Non-string defaults fail with a clear error.
  • If both index.js and index.html exist, index.js wins.
  • index.js is reserved for SwatchKit and is not copied as a preview asset. Other .js files in the folder are copied (e.g. demo.js for browser-side scripts).
  • For JavaScript swatches that use import / export, set "type": "module" in your package.json. Without that, the swatch runs as CommonJS and import syntax is a parse error.

The renderer contract

A component renderer is a function with a specific shape. This is what makes the shared-between-app-and-library pattern work.

  • Signature: function renderX(props) → string
  • Pure: no side effects, no I/O, no global state
  • Deterministic: same input → same output, every time
  • Self-contained output: the returned string is a complete HTML fragment

Props are an object, destructured with defaults for optional fields. Document the shape with a comment above the function so callers know what to pass.

Naming: render<ComponentName>. Verb-first makes the role obvious.

// Pure, deterministic, no imports beyond the function's needs.
export function renderButton({ label, href, variant = "primary" }) {
  const className = variant === "outline" ? "button outline" : "button";
  return href
    ? `<a class="${className}" href="${href}">${label}</a>`
    : `<button class="${className}">${label}</button>`;
}

For a full project that uses this contract end-to-end, see the hand-rolled app setup guide.

Two ways to ship CSS

SwatchKit's CSS behavior is controlled by cssCopy in the config. The default is true; for integrated apps you'll usually want false.

Self-contained (cssCopy: true)

SwatchKit copies cssDir into dist/swatchkit/css/. The pattern library is fully self-contained — you can serve just dist/swatchkit/ anywhere.

// swatchkit.config.js
export default { cssDir: "./src/css", cssCopy: true };
dist/swatchkit/
├── css/main.css            ← copied from src/css/
└── index.html

Use this when the pattern library is the deliverable, or when you're just kicking the tires.

Integrated (cssCopy: false)

SwatchKit skips the copy and writes <link> tags pointing at cssPath instead. You put the CSS in dist/css/ once, the library references it, and your app's HTML references it too. No duplication.

// swatchkit.config.js
export default {
  cssDir: "./src/css",
  cssCopy: false,
  cssPath: "../css/",
};
dist/
├── index.html              ← your app, links ./css/main.css
├── css/main.css            ← one copy, shared
└── swatchkit/
    ├── index.html          ← links ../css/main.css
    └── preview/...

cssPath is the path from swatchkit's HTML to the CSS. The default (when omitted) is ../<basename of cssDir>/ — so cssDir: "./src/css" defaults to "../css/". For most integrated projects, that's all you need. Set it explicitly only if your build puts CSS somewhere other than dist/css/.

For an integrated app, your package.json typically chains both steps. The canonical setup uses esbuild for the CSS+JS bundling step — see the hand-rolled app setup guide for the full reference:

{
  "scripts": {
    "clean": "node scripts/clean.js",
    "build:site": "node scripts/build-site.js",
    "build:swatchkit": "swatchkit",
    "build:assets": "node scripts/build-assets.js",
    "build": "npm run clean && npm run build:site && npm run build:swatchkit && npm run build:assets",
    "patterns": "swatchkit"
  }
}

build:swatchkit runs first so the freshly regenerated src/css/utilities/utilities.css (the utility classes derived from your @swatchkit token blocks) gets picked up by build:assets. Even with cssCopy: false, SwatchKit still regenerates that utilities file inside cssDir; it only skips copying CSS into dist/swatchkit/css/. Your tokens.css is never modified — it's the source of truth.

If you just want to kick the tires: cp -r src/css dist/css works in place of build-assets.js, but it doesn't bundle @imports and doesn't minify — use it for a prototype, then switch to the esbuild flow for anything beyond that.

For a complete walkthrough of an integrated setup (with the full clean.js, build-site.js, and build-assets.js scripts), see the hand-rolled app setup guide.

Configuration

swatchkit.config.js at the project root. All options are optional.

export default {
  // Pattern source directory (default: "./swatchkit")
  input: "./patterns",

  // Output directory (default: "./dist/swatchkit")
  outDir: "./dist/patterns",

  // CSS source directory.
  // `swatchkit init` defaults this to "./src/css".
  // No-config builds fall back to "./css".
  cssDir: "./src/css",

  // CSS files scanned for @swatchkit token blocks (supports a trailing * glob).
  // Default: ["<cssDir>/global/tokens.css", "<cssDir>/tokens.css", "<cssDir>/tokens/*.css"].
  // Add theme files explicitly when needed. See docs/tokens.md.
  tokenSources: ["./src/css/global/tokens.css", "./src/css/theme.css"],

  // Copy cssDir into outDir/css. Set false to reference CSS via cssPath
  // instead. See "Two ways to ship CSS" above.
  cssCopy: true,

  // Path from swatchkit's HTML to the CSS. Only used when cssCopy is false.
  // Default: "../<basename of cssDir>/"
  cssPath: "../css/",

  // Exclude files from the pattern library (supports globs).
  exclude: ["*.test.js"],

  // Control sidebar section order and swatch order inside each section.
  // Order lists are partial: listed slugs come first; unlisted items follow
  // alphabetically. Ordering runs after exclude and tokenDocs filters.
  order: {
    sections: ["tokens", "components", "compositions", "utilities", "patterns"],
    swatches: {
      tokens: ["aries-brand-colors", "colors", "fonts"],
      components: ["button", "card"],
    },
  },

  // Customize generated token documentation. Utilities still generate from
  // parsed tokens even when a token doc page is hidden.
  tokenDocs: {
    showSource: false, // default for generated token docs
    colors: {
      columns: ["name", "value", "customProperty"],
      columnLabels: { customProperty: "CSS variable" },
      includeLabels: ["Brand Colors"],
      // excludeLabels: ["Internal Colors"],
    },
    spacing: { enabled: false },
  },

  // Override the default HTML renderers.
  // renderSidebarSection: ({ category, categorySlug, items }) => string,
  // renderSwatchSection: ({ slug, name, category, categorySlug, description, previewHref, content, escapedContent, sourceKind, showSource }) => string,
};

SwatchKit looks for the config in this order: swatchkit.config.cjs (CJS), swatchkit.config.mjs (ESM), swatchkit.config.js (depends on project's package.json#type). Rename to .cjs if you need CJS syntax in an ESM project.

Ordering sections and swatches

Use order when you want important sections or swatches to appear first in the sidebar. Entries are slugs: section folder names such as components or compositions, tokens for generated token docs, and patterns for root-level swatches.

order is not an include list. It only sorts items that still exist after exclude and tokenDocs filters run. Unknown slugs are ignored, and wildcards are not supported in order lists.

When an order list is configured, listed slugs come first in the given order. Any unlisted items follow alphabetically.

CLI

swatchkit [command] [options]

| Command | What it does | | :--- | :--- | | swatchkit init | Create swatchkit.config.js and scaffold CSS blueprints, layout templates, and a starter tokens.css. Prompts for cssDir; pass --cssDir to skip the prompt. Status report if already initialized. | | swatchkit init --app | Also scaffold an integrated esbuild app starter (build scripts, shared renderers, home page, two example swatches, watch-enabled package.json). | | swatchkit init --force | Overwrite all managed files (with .bak backups). | | swatchkit init --dry-run | Show what would change, write nothing. | | swatchkit (default) | Build the pattern library. |

| Flag | Short | What it does | | :--- | :--- | :--- | | --app | | With init: scaffold the integrated esbuild app starter. | | --watch | -w | Rebuild on file change. | | --config | -c | Path to config file. | | --input | -i | Pattern source dir (default: swatchkit/). | | --outDir | -o | Output dir (default: dist/swatchkit). | | --cssDir | | CSS dir for init (default prompt: src/css). | | --force | -f | Overwrite config + blueprints (with backups). | | --dry-run | | init: report without writing. | | --help | -h | | | --version | -v | |

How it works

swatchkit (the build command) does four things:

  1. Parses @swatchkit token blocks from the CSS files in tokenSources (your hand-written tokens — the source of truth). It regenerates css/utilities/utilities.css (utility classes derived from those tokens) and the token-documentation HTML under swatchkit/tokens/. Your token CSS is never modified — only utilities.css is generated. See docs/tokens.md.
  2. Copies CSS from cssDir to outDir/css (only when cssCopy: true).
  3. Scans swatchkit/ for swatches, renders each one. Static index.html swatches go through unchanged. Dynamic index.js swatches are imported and executed for their HTML. Sibling assets are copied alongside.
  4. Writes outDir/index.html (the library) and one outDir/preview/{section}/{id}/index.html per swatch (full-screen previews).

In watch mode, SwatchKit compares generated content against existing files and skips the write when nothing has changed — so most rebuilds don't touch your CSS directory, and there's no infinite-rebuild loop when running alongside onchange or framework dev servers.

What swatchkit owns vs what you own

| Path | Owned by | Notes | | :--- | :--- | :--- | | src/css/global/tokens.css | you | Source of truth for design tokens. Hand-edit freely (@swatchkit blocks). | | src/css/main.css | you | Your entry point. | | src/css/global/variables.css | you | Edit var() references if you rename tokens. | | src/css/global/elements.css | you | Same. | | src/css/utilities/utilities.css | swatchkit | Generated every build from your token blocks. Do not edit. | | src/css/swatchkit-ui.css | you | Docs UI styling. Safe to customize. | | src/css/swatchkit-preview.css | you | Preview page styling. Safe to customize. | | swatchkit/_swatchkit.html | you | Layout template. init --force overwrites it. | | swatchkit/_preview.html | you | Preview template. Same caveat. | | swatchkit/tokens/*.html | swatchkit | Regenerated every build. | | swatchkit/swatches/**/index.html | you | Your swatches. | | swatchkit/swatches/**/index.js | you | Your JS swatches. | | swatchkit/swatches/**/description.html | you | Optional, shown above the iframe in the library. | | swatchkit/swatches/**/* (other files) | you | Copied as sibling assets. |

Using with a framework

SwatchKit only ever writes inside its own output directory — never the rest of dist/. If your framework (Vite, Astro, etc.) cleans dist/ on build, run it first and swatchkit after:

"scripts": {
  "build": "vite build && swatchkit"
}

In watch mode, SwatchKit polls for its output directory and rebuilds if it was wiped by an external tool.

Design tokens

SwatchKit is CSS-first: tokens live in plain CSS that you own and hand-edit. You mark groups with @swatchkit comment blocks, and SwatchKit reads them to document each group and to generate utility classes.

/* src/css/global/tokens.css */
:root {
  /* @swatchkit colors "Brand Colors" */
  --brand: #3b49df;
  --brand-hover: oklch(from var(--brand) calc(l + 0.06) c h);
  /* @swatchkit end */
}

This documents a "Brand Colors" page and generates .color:brand, .background-color:brand, etc. into css/utilities/utilities.css. Relational values (oklch(from …), var(…), clamp(…)) are preserved verbatim in the docs, so relationships are never flattened away.

Generated token docs hide their View source details by default. Use tokenDocs to re-enable source, disable specific token doc pages, include or exclude token labels, or customize color-table columns without removing the @swatchkit markers from your CSS.

Supported token types: colors, spacing, text-sizes, text-weights, text-leading, fonts, viewports. Blocks can live under any selector (and inside @layer / @media), so theme variants are documented by wrapping each theme's region in its own block.

Fluid type and spacing use plain, universally-supported clamp() (no build-time generator), tweakable via --vw-min / --vw-max / --root-base config variables.

See docs/tokens.md for the full reference: marker syntax, tokenSources, every type's utilities, and the fluid clamp() pattern.

Cascade layers

The scaffolded main.css declares a cascade-layer order so the cascade is predictable — a later layer always wins over an earlier one, regardless of selector specificity:

@layer reset, tokens, elements, compositions, swatches, app, utilities;

| Layer | Contains | | :--- | :--- | | reset | browser reset | | tokens | design tokens + variables | | elements | bare element defaults | | compositions | layout primitives (flow, cluster, sidebar, …) | | swatches | your component styles | | app | app/page-specific CSS (overrides components) | | utilities | generated token utilities — the final word |

utilities is declared last, so utility classes win over component and app styles without !important. Add a utility class in your markup and it takes effect. Plain unlayered CSS still beats every layer — that's your escape hatch for the rare override.

Acknowledgements

The CSS compositions included by default are adapted from Every Layout by Heydon Pickering and Andy Bell.