🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

knobkit

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knobkit

Your AI app, live as you type — declare widgets, write event handlers, done. The same file runs in the browser or on a stateless Node server.

latest
Source
npmnpm
Version
0.0.7
Version published
Maintainers
1
Created
Source

knobkit

CI npm version license

Your AI app, live as you type. Declare widgets, write on(event, handler) functions — done. The same demo.tsx runs entirely in the browser (mount) or on a stateless Node server (serve); change the last line to swap. The browser owns all state — the server keeps none, so there are no sessions.

knobkit.dev — 30-second tour + a live playground (nothing to install).

🛠️ Building with an AI agent? The knobkit-skills Agent Skill is the recommended way to scaffold and build a knobkit app fast — works in Claude Code or any Agent Skills–compatible agent.

knobkit — scaffold, run the dev server, edit, watch the browser update live

import { knobkit, mic, output } from "knobkit";
import { pipeline } from "@huggingface/transformers";

const transcriber = await pipeline("automatic-speech-recognition", "onnx-community/whisper-base.en");
const recorder = mic();
const transcript = output();

const app = knobkit({ title: "Transcribe", widgets: [recorder, transcript] });

app.on(recorder.clip, async (samples) => {
  const { text } = (await transcriber(samples)) as { text: string };
  transcript.set(text.trim() || "(silence)");
});

app.serve(); // runs Whisper on Node — change to app.mount("#root") to run it in the browser via WebGPU

See examples/ — chatbots, image captioning, live transcription, webcam filters; each a single demo.tsx.

Quick start

npm create knobkit@latest my-app   # prompts mount (browser) vs serve (node); or pass --mount / --serve
cd my-app && npm install && npm run dev

Already have a project? npm install knobkit. Requires Node ≥ 22.

CLI

knobkit dev         # dev server — auto-detects the tier from mount()/serve() in the entry
knobkit build       # build a mount app to static files in dist/
knobkit serve       # run a serve app
knobkit playground  # split-pane REPL: editor + live preview, file picker, edits round-trip to disk

Entry = your package.json "main" (override with knobkit dev other.tsx). --mount / --serve force the tier; --port <n> sets the port (playground default 4317).

How it works

A handler is a plain on(event, async fn). Inside it you do exactly three things:

  • read widget state with async getters — await box.value(), await convo.history() (a real round-trip on serve);
  • write with structured setters — out.set(v), convo.say(m), log.push(line);
  • produce by returning an event from the handler (re-emitted, like a user action).

setup(fn) runs once per session for async startup (load weights, fetch data). widget.busy(fn) wraps a handler in a transient working span (a bar; drops the widget's input while running); disable() / enable() is the persistent version. Widget methods only work inside a handler or setup.

mount("#root")serve()
on(...) handlersrun in the browserrun on a stateless Node server
transportlocal callsocket.io
use whenfits client-side (incl. WebGPU models)needs the server (large models, secrets, native deps)

mount builds to static files you can host anywhere; serve adds no session state. Widgets, handlers, and methods are identical across both — only the last line changes.

Widgets

Value inputs all share one shape: a changed event whose payload is the value, plus await w.value() and w.set(v). (No .submitted/.uploaded — listen on changed, or use a button's .clicked and read await input.value().)

Factory (defaults)changed valueNotes
text({ placeholder?, lines? })stringlines = textarea rows (default 1)
number({ value?, min?, max? })numbernumeric stepper (init 0)
slider({ value?, min?, max?, step? })numbermin 0, max 100, step 1
dropdown({ choices, value? })stringchoices: string[]; value defaults to choices[0]
checkbox({ label?, value? })booleansingle toggle
checkboxGroup({ choices, value? })string[]multi-select
radio({ choices, value? })stringsingle-select
upload({ accept? })string | nullvalue is a data URL; accept default image/*

Other inputs:

Factory (defaults)EventsMethods
button({ label })clickedset({ label })
mic({ every?, control?, hold? })clip (Float32Array), toggledstart(), stop(), await toggle(), await live(). every ms emits a clip every N ms (0 = hold/toggle only)
webcam({ every?, control?, preview?, facing? })frame (data URL), toggledsame controls. every ms emits a frame every N ms (0 = preview only); facing "user"/"environment"
chat({ placeholder?, voice?, images?, markdown? })sent ({ text, image? }), recordedawait history(), say(msg), append(token). markdown renders assistant replies; images/voice add attach/talk buttons

Outputs (write-only; set(...) replaces the value):

Factory (defaults)Write / methodsNotes
output({ format? })set(text)format: "markdown" renders GFM
json()set(value)pretty-printed JSON
log()push(line), await all()append-only lines
label()set(string | { label?, confidences? })classifier result; confidences: { label, score }[] → bars
html({ value? })set(markup)raw HTML
image()set(urlOrDataUrl)one image
gallery()set(items), add(item)item: { src, caption? }
audio({ autoplay? }) / video({ autoplay?, loop? })set(src)URL or data URL
progress({ label? })set(value, label?)value is 0..1
file()set({ name?, url } | url)offer a download
annotatedImage()set(src, annotations?, colorMap?)Annotation: { label, box?: [x0,y0,x1,y1], mask? }
highlightedText()set(spans, colorMap?)span: { text, label? } (label omitted = plain)
chart({ x, y, kind?, data?, maxHeight? })await data(), setData(rows), push(point)x = category key; y = key or string[]; kind bar/line/area
frame({ src?, doc?, sandbox?, title? })load(url), show(doc), clear()iframe; event loaded

Editable or read-only:

Factory (defaults)EventsMethods
code({ value?, language?, editable?, wrap? })changed (string)await value(), set(src), setLanguage(lang). editable: false = viewer; wrap soft-wraps
table({ columns?, rows?, editable?, maxHeight? })edited ({ row, key, value })await data(), setRows, setColumns, addRow, setCell. Column: { key, label?, type?, width? }

Custom: widget({ state, view, fold?, behavior? }) builds a widget from scratch — state is its data, view(state, emit) renders it, fold applies events to state.

Layout

widgets is a tree of widget objects (no keys/strings). An array is an implicit col:

knobkit({ widgets: col(photo, row(size, go), caption) });
grid([a, b, c, d], { cols: 2 });
tabs([{ label: "One", content: a }, { label: "Two", content: b }]);
accordion({ label: "Advanced", open: false }, x, y);

Containers are widgets whose state is their arrangement, so a handler can restructure the UI at runtime — panel.add(chart), await panel.remove(sidebar).

Theming

Set on knobkit({ … }), or flip at runtime with setTheme / setDensity:

  • theme"system" (default) | "light" | "dark".
  • density"xs" | "sm" | "md" | "lg" | "xl" (default md) — spacing, control sizes, radii, type.
  • fill: true — full-bleed shell that fills the viewport (for split panes / dashboards) instead of the centered card.

Everything renders from CSS custom properties (--pu-bg, --pu-accent, --pu-gap, the --pu-series-* chart palette, …); theme/density just remap them, so one switch restyles the whole kit (including the code editor, table, and chart). The attributes inherit, so you can scope them to one container; to rebrand, override the tokens in your CSS (e.g. :root { --pu-accent: rebeccapurple }).

Develop

pnpm install
pnpm -F knobkit build   # library + browser client bundle
pnpm -F knobkit test    # vitest
pnpm typecheck          # all packages

See CLAUDE.md for the architecture and how to add a widget.

License

MIT

Keywords

widgets

FAQs

Package last updated on 10 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts