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

@ktbsh/sdk

v0.2.0

Published

Compiler-driven toolkit for building design-system components as Web Components (with React wrappers).

Readme

@ktbsh/sdk

Kitbash is a small, compiler-driven toolkit for building design-system components once and shipping them as native Web Components — with optional React wrappers for free.

You author a single TypeScript config (defineComponent). The CLI evaluates it, then emits:

  • Vanilla custom elements (Shadow DOM + uhtml DOM diffing, uhtml bundled in)
  • React wrappers (typed JSX props + event bridging)
  • A Custom Elements Manifest (custom-elements.json) for IDE autocomplete

This project is early (0.1.x) and intentionally experimental. The goal is a practical loop for trying design-system ideas: define → compile → drop into React, Svelte, or plain HTML. APIs and output shape will evolve as real usage teaches what matters.

Supported surface (what works / what does not): monorepo docs/SUPPORTED.md.
Audit / improvement notes: monorepo docs/improvements/.


Why this exists

Most design-system work either:

  1. Binds hard to one framework, or
  2. Ships hand-written Web Components that are painful to maintain and wrap.

Kitbash tries a middle path: author once as data + render functions, then let a compiler produce the boring runtime glue (property/attribute sync, stylesheets, form association hooks, React refs/events, CEM metadata).

You get a short authoring surface; consumers get real custom elements that work outside your build tool.


Features

| Area | What you get today | |------|--------------------| | Author once | defineComponent({ tag, props, state, styles, events, render }) | | Vanilla output | Minified custom element with Shadow DOM and constructable stylesheets | | React output | forwardRef wrapper, onKitbashChange, children → light DOM / slots | | Theming | CSS variables on :host, shadow part hooks, optional tokens.json | | Forms | formAssociated + ElementInternals, optional delegatesFocus | | DX | kitbash init scaffold, kitbash build, CEM for editors | | Runtime deps for consumers | Vanilla bundles bake in uhtml — no extra install for end apps |

Not (yet): full Svelte wrapper codegen (use vanilla tags), frameworks config toggles, built-in browser server/HMR, Storybook plugin, etc. (see Known limitations (0.1.x)).


Requirements

  • Bun ≥ 1.0 — required for the CLI and compiler (engines.bun)
  • A project that can import TypeScript component sources at build time (Bun does this natively)

The published CLI is built for the Bun runtime (bun build … --target bun). Use bunx kitbash or a global Bun install, not Node’s npx, for reliable results.


Quick start

1. Scaffold a design system

bun add -g @ktbsh/sdk
kitbash init my-design-system
cd my-design-system
bun install
bun run dev     # watch + rebuild (or: bun run build once)

Or without a global install:

bunx @ktbsh/sdk init my-design-system

2. What you get

my-design-system/
├── kitbash.config.ts      # optional: components / tokens / outDir
├── package.json           # "build": "kitbash build"
└── src/
    ├── tokens.json        # design tokens → CSS variables on :host
    └── components/
        ├── button.ts      # example component
        └── input.ts       # form-associated example

After bun run build:

dist/
├── custom-elements.json
├── vanilla/
│   ├── button.js          # browser-ready custom element (uhtml inlined)
│   ├── button.src.js      # intermediate source (generated)
│   ├── input.js
│   └── input.src.js
└── react/
    ├── button.js
    ├── button.d.ts
    ├── input.js
    └── input.d.ts

3. Define your own component

// src/components/badge.ts
import { defineComponent } from '@ktbsh/sdk';

export default defineComponent({
  tag: 'my-badge',
  props: {
    label: { type: String, default: '' },
    tone: { type: String, default: 'neutral' },
  },
  styles: `
    :host {
      display: inline-flex;
      --badge-bg: #eaeaea;
      --badge-color: #111;
    }
    span {
      background: var(--badge-bg);
      color: var(--badge-color);
      padding: 0.15rem 0.5rem;
      border-radius: 999px;
      font-size: 0.75rem;
    }
    .danger { --badge-bg: #fee2e2; --badge-color: #991b1b; }
  `,
  render({ props, html }) {
    return html`
      <span part="badge-root" class=${props.tone}>${props.label}</span>
    `;
  },
});
bun run build
# ✅ Compiled <my-badge>

4. Use the output

Vanilla / any framework that supports custom elements:

<script type="module">
  import './dist/vanilla/badge.js';
</script>
<my-badge label="New" tone="danger"></my-badge>

React 19:

import { MyBadge } from './dist/react/badge.js';

export function App() {
  return <MyBadge label="New" tone="danger" />;
}

Svelte 5 (import the vanilla element; bind with DOM events):

<script lang="ts">
  import './dist/vanilla/input.js';
  let val = $state('');
</script>

<kitbash-input
  value={val}
  onkitbash-change={(e) => (val = e.detail.props.value)}
  placeholder="Type…"
></kitbash-input>

How it works

Kitbash uses an evaluation compiler, not an AST rewrite of your source files.

  src/components/*.ts
         │
         │  Bun dynamic import()
         ▼
  default export = ComponentConfig
         │
         │  serialize render/events + emit class source
         ▼
  dist/vanilla/*.src.js  ── Bun.build (minify, resolve uhtml) ──►  *.js
         │
         └── also emit React wrappers + custom-elements.json
  1. defineComponent is a typed identity function — it returns your config object as-is.
  2. kitbash build loads each file under src/components/, reads export default.
  3. The compiler stringifies render and events handlers and stitches them into a custom element class.
  4. Styles (plus flattened tokens) become a constructable CSSStyleSheet on the shadow root.
  5. Bun.build bundles uhtml into the vanilla artifact so consumers don’t depend on it.
  6. React wrappers re-export the vanilla CE and bridge kitbash-change / click.

Runtime model (generated element)

| Concern | Behavior | |---------|----------| | Props | Reflected as attributes; String / Number / Boolean coercion | | State | Internal _state | | Updates | commit({ props?, state? })one re-render + one kitbash-change. setProps / setState are thin wrappers around commit. | | Re-render | uhtml render() into shadowRoot | | Events | Selectors like 'click button' or 'input input'; handlers receive { props, state, commit, setProps, setState } | | Change event | User-driven commit / setProps / setState only (not external el.value = … from the parent) | | Forms | If formAssociated, ElementInternals + setFormValue on value; basic validity for required / invalid |

Why uhtml v4?

The compiler pins [email protected]. v5’s signal rewrite and some conditional-array behaviors were intentionally avoided for predictable Shadow DOM updates. Don’t casually bump this dependency in forks without re-testing input focus and list rendering.


Authoring API

Hard rule: no outer closures

render and events handlers are serialized with .toString() into the generated custom element. They do not keep a real JS closure over your module.

Snapshots, not deep clones: commit / kitbash-change / handler props & state are shallow copies. Nested objects/arrays inside props remain shared references — mutate them only via commit, or treat them as read-only.

| OK | Not OK | |----|--------| | Use props, state, commit, setProps, setState, html, DOM APIs | import { x } from '…' then use x inside render / events | | Inline strings, numbers, simple logic | Capture outer const theme = … or helpers from the same file | | Call methods on e.target | Rely on module-level variables |

If you need shared helpers, either inline them or wait for a future “runtime helpers” package — do not close over imports.

import { defineComponent, type ComponentConfig } from '@ktbsh/sdk';

export default defineComponent({
  tag: 'my-element',           // required custom element tag
  formAssociated?: boolean,    // ElementInternals + form value sync for `value`
  delegatesFocus?: boolean,    // attachShadow({ delegatesFocus })
  props?: {
    name: { type: String | Number | Boolean, default: unknown }
  },
  state?: Record<string, unknown>,
  styles?: string,             // CSS injected into constructable stylesheet
  events?: {
    // key: "eventName" or "eventName css-selector"
    'click button'(e, { commit, setState }) { /* … */ }
  },
  render({ props, state, commit, setProps, setState, html }) {
    return html`…`;           // uhtml template (html tagged template)
  },
});

Props

  • Declared props become getters/setters and observedAttributes.
  • Boolean: presence of the attribute (or true) → true; missing / "false" → falsey handling as in the generated code.
  • Number: attribute strings are coerced with Number(...).
  • Defaults apply when the attribute is removed.
  • External writes (el.value = 'x' or React value={…}) re-render but do not fire kitbash-change (the parent already knows).

commit / setProps / setState (fast path)

| API | Use for | |-----|---------| | commit({ props?, state? }) | Preferred — batch props + state in one update and one kitbash-change | | setProps({ … }) | Props only (wrapper around commit) | | setState({ … }) | UI state only — open/hover/touched (wrapper around commit) |

Controlled input (scaffold pattern):

events: {
  'input input'(e, { commit }) {
    const t = e.target as HTMLInputElement;
    commit({
      props: { value: t.value },
      state: { touched: true },
    });
  },
},

Consumers read e.detail.props.value (and e.detail.state) on kitbash-change / React onKitbashChange.

Events map

Keys are space-separated: eventName optional selector.

events: {
  'input input'(e, { commit }) {
    commit({ props: { value: (e.target as HTMLInputElement).value } });
  },
}

Handlers are re-bound after every update (previous listeners cleaned up). Prefer stable selectors.

Slots

Use standard <slot> in your template. React wrappers pass children into the custom element’s light DOM so the browser projects them into slots.

render({ html }) {
  return html`<button part="button-root"><slot></slot></button>`;
}

Theming

1. CSS variables on :host (in component styles):

:host {
  --kitbash-btn-bg: #0070f3;
}
button { background: var(--kitbash-btn-bg); }

Consumers override from the outside:

my-button {
  --kitbash-btn-bg: rebeccapurple;
}

2. Shadow parts for deeper styling:

<button part="button-root">…</button>
my-button::part(button-root) {
  text-transform: uppercase;
}

3. Design tokens file — optional (default src/tokens.json, overridable in config):

{
  "colors": { "primary": "#0070f3" },
  "spacing": { "md": "16px" }
}

Flattened into :host variables, e.g. --colors-primary, --spacing-md, and prepended to every component’s stylesheet at compile time.

Form-associated components

export default defineComponent({
  tag: 'kitbash-input',
  formAssociated: true,
  delegatesFocus: true,
  props: {
    name: { type: String, default: '' },
    value: { type: String, default: '' },
    required: { type: Boolean, default: false },
    invalid: { type: Boolean, default: false },
  },
  events: {
    'input input'(e, { commit }) {
      commit({ props: { value: (e.target as HTMLInputElement).value } });
    },
  },
  // …
});

Generated behavior includes:

  • static formAssociated = true
  • attachInternals() and setFormValue when value is assigned (via commit / setProps / property)
  • Basic setValidity for required / invalid props

SDK vs design system: Kitbash wires platform form participation and focus delegation. Labels, error copy, live regions, and full WCAG product patterns belong in your design system components — not the compiler.

React wrapper contract

| Prop / event | Meaning | |--------------|---------| | Declared props | Passed through to the custom element | | children | Light DOM → slots | | onClick (and other native DOM handlers) | Forwarded on the host — React 19 binds them (not double-wrapped) | | onKitbashChange | Bridges custom kitbash-change (e.detail.props / e.detail.state) | | ref | Callback refs and RefObject both supported |


CLI reference

kitbash init <project-name>   Scaffold templates/default into a new folder
kitbash build                 Compile components (config optional)
kitbash dev                   Watch + rebuild on component/token/config changes
kitbash                       Print help

| Command | Notes | |---------|--------| | init | Project name must be a single path segment (no .. / nested paths). Refuses if the directory exists. Rewrites workspace:* SDK deps to the published version. Adds build + dev scripts. | | build | Loads optional kitbash.config.ts, compiles under process.cwd(). | | dev | Initial build, then watches components + tokens + config. Re-binds watchers after each build (config path changes apply). Debounced. Does not start a browser server (use Vite/sandbox separately). Run from the design-system package root. |

Add a script in your design-system package.json:

{
  "scripts": {
    "build": "kitbash build"
  },
  "dependencies": {
    "@ktbsh/sdk": "^0.1.1"
  }
}

Project layout & kitbash.config.ts

Defaults (when no config file):

| Path | Role | |------|------| | src/components/*.ts | One default-exported component per file | | src/tokens.json | Optional design tokens | | dist/ | Build output |

Optional kitbash.config.ts (or .js) is loaded by kitbash build:

export default {
  components: './src/components', // relative to project root
  tokens: './src/tokens.json',
  outDir: './dist',
  frameworks: ['react', 'svelte'], // reserved — 0.1.x always emits vanilla + react
};

| Key | Applied? | |-----|----------| | components | Yes | | tokens | Yes | | outDir | Yes | | frameworks | No (logged as reserved) |


Packaging your design system

After kitbash build, publish your package (not the SDK) with something like:

{
  "name": "@you/ui",
  "type": "module",
  "exports": {
    "./vanilla/*": "./dist/vanilla/*",
    "./react/*": "./dist/react/*",
    "./custom-elements.json": "./dist/custom-elements.json"
  },
  "files": ["dist"]
}

Consumers then:

import '@you/ui/vanilla/button.js';
// or
import { MyButton } from '@you/ui/react/button.js';

Point VS Code / CEM tooling at custom-elements.json for tag autocomplete where supported.


Troubleshooting

kitbash: command not found

  • Install with Bun: bun add -g @ktbsh/sdk, or use bunx @ktbsh/sdk …
  • Ensure the Bun global bin directory is on your PATH

Build fails / empty output

  • Run from the project root (the folder that contains src/components)
  • Each component file must export default defineComponent({ tag: '…', … })
  • Only .ts / .js files in src/components are compiled
  • Missing src/components → warning and no output (exit still succeeds)

“No valid default ComponentConfig”

  • Default export must include a tag string
  • Avoid named-only exports without export default

Attribute / prop not updating the UI

  • Prop must be listed under props (so it is observed)
  • Prefer setting the property from JS (el.value = 'x') when types matter; Booleans are special-cased
  • Confirm your render reads props.* (not a closed-over stale value)

Input loses focus or value on each keystroke

  • Prefer uhtml property binding: .value=${props.value} (as in the scaffold)
  • Avoid replacing the whole input via unstable keys or recreating nodes unnecessarily
  • Do not upgrade the SDK’s uhtml pin without testing this path

Form submit does not include the field

  • Set formAssociated: true
  • Keep a value prop and update it on input (property + setFormValue)
  • Ensure the control has a name attribute/prop if you use FormData

React: onKitbashChange never fires

  • It only fires when commit / setProps / setState run inside the component (not when React sets value={…} from outside)
  • For controlled inputs use commit({ props: { value } }) in the input handler (see scaffold input.ts), then onKitbashChange={(e) => setVal(e.detail.props.value)}

React types / JSX unknown tag

  • Import the generated *.d.ts side (or the wrapper module) so the JSX IntrinsicElements augmentation loads
  • Use React 19-friendly tooling; wrappers target modern React

Styles don’t apply from the parent page

  • Shadow DOM encapsulates plain element selectors — use CSS variables or ::part(...)
  • Tokens only apply if the tokens file exists and parses as JSON at build time (default path or tokens in config)

Tokens ignored

  • Default path is src/tokens.json unless kitbash.config.ts sets tokens
  • Missing file → no token CSS (warning if you set a custom tokens path)
  • Invalid JSON logs a warning and continues without tokens

Init: “Directory already exists” / invalid name

  • Pick a new folder name; only a single directory segment is allowed (my-ds, not ../my-ds)

Using with Vite / other bundlers

  • Import built dist/vanilla/*.js or dist/react/*.js from the app — don’t point the app at raw Kitbash component sources unless you know Bun/TS evaluation is available
  • Vanilla files are already browser-minified bundles; React wrappers still import react and the sibling vanilla module

Known limitations (0.1.x)

Be aware of these before relying on Kitbash in production:

  1. frameworks in config is reserved — vanilla + react always emitted; no Svelte/Vue wrappers yet (use vanilla CE).
  2. Event map rebinds every update — fine for small trees; measure if you bind many nodes.
  3. CEM is minimal — tags/attributes only; no slots/events/CSS parts documentation yet.
  4. Form validity is basicrequired / invalid only; no full constraint validation API surface. Product a11y (labels, announcements) is design-system work.
  5. Function serialization — hard rule above; no real closures over module scope.
  6. Bun-only toolchain — Node is not a supported host for the CLI today.
  7. kitbash dev is compile-only — no built-in Vite/HMR server (pair with your app’s dev server; monorepo sandbox still uses bun run dev).

Ideas for improvement

Contributions and experiments welcome. High-value directions:

| Idea | Why | |------|-----| | Dev server integration | Optional Vite plugin / HMR for kitbash dev | | frameworks toggles in config | Opt out of react emit / future targets | | Svelte / Vue wrapper codegen | First-class DX beyond vanilla tags | | Richer CEM | Events, slots, CSS parts/properties for docs tools | | Stable public runtime helpers | Shared utilities without relying on serialized closures | | CSS / token pipeline | Themes, dark mode maps, reference to CSS files | | Source maps & better errors | Map compile failures back to authoring files | | Node-compatible CLI build | Wider install story if Bun-only is a blocker | | Broader compiler snapshots | Expand beyond form/change + runtime contract tests | | exports map in scaffold | Publish-ready package.json from init | | Strip or gitignore *.src.js | Cleaner publish artifacts |

Done recently: controlled input via commit + React bridge + event detail snapshots + contract tests (see changelog / recent commits).

If you try Kitbash on a real system, issues and “this surprised me” notes are especially useful — early APIs should bend toward real workflows.


Development (this monorepo)

If you are hacking on the SDK itself (not only consuming it from npm):

# from repo root
bun install
bun run --filter @ktbsh/sdk build   # or: cd packages/sdk && bun run build
  • Compiler: packages/sdk/src/compiler.ts
  • CLI: packages/sdk/src/cli.ts
  • Public API: packages/sdk/src/index.ts (defineComponent)
  • Template copied by init: packages/sdk/templates/default
  • Playground: /sandbox (React, Svelte, Vanilla side by side against templates/default/dist)

Versioning

Current line: 0.1.x — expect breaking changes while the compiler and authoring API settle. Pin versions in apps ("@ktbsh/sdk": "0.1.1") if you need stability.


License

MIT


Links