
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
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.
knobkit.dev — watch the 30-second tour, then edit a real app in the live playground (nothing to install).
Your AI app, live as you type. Declare widgets, write event handlers — done. The same file runs entirely in the browser — no server at all — or with handlers on a stateless Node server. Swap one line.
import { knobkit, mic, output } from "knobkit";
import { pipeline } from "@huggingface/transformers";
// A local Whisper model — runs on the Node server with serve(), or in the browser with mount().
const transcriber = await pipeline("automatic-speech-recognition", "onnx-community/whisper-base.en");
const recorder = mic();
const transcript = output();
const app = knobkit({
title: "Transcribe",
description: "Record audio; a local Whisper model turns it into text.",
widgets: [recorder, transcript],
});
app.on(recorder.clip, async (samples) => {
const { text } = (await transcriber(samples)) as { text: string };
transcript.set(text.trim() || "(silence)");
});
app.serve();
That's a complete app — record a clip, get a transcript. app.serve() runs Whisper on Node; change
that one line to app.mount("#root") and the identical file runs the model in the browser via WebGPU.
From there the same shape scales to a full local
voice assistant (Whisper →
Qwen → Kokoro) or live meeting transcription —
see examples/.
Same authoring feel — declare inputs, write a function, get an app — different architecture:
mount apps build to static files —
host them anywhere, run WebGPU models client-side), then move handlers to a server when you need
secrets, large models, or native deps — by changing the last line.on(event, async fn) functions. No full-script
re-execution on every interaction, no reactive graph to fight, no widget-key gymnastics.{ type, payload } — never a
copy of state. An attribute a handler never reads (say, generated audio) never leaves the browser.npm create knobkit@latest my-app # choose mount (browser) or serve (node)
cd my-app
npm install
npm run dev
Skip the prompts with npm create knobkit@latest my-app -- --mount (or --serve). Already have a
project? npm install knobkit. Requires Node ≥ 22.
The knobkit package installs a knobkit command:
knobkit dev # dev server — auto-detects mount vs serve
knobkit build # build a browser (mount) app to dist/
knobkit serve # run a server (serve) app
knobkit playground # split-pane REPL — editor on the left, live app on the right
The entry file is the "main" field of your package.json — like vite, the manifest names the
entry. Pass a file to run something else: knobkit dev other.tsx. Flags: --mount / --serve
force a mode, --port <n> sets the dev-server port. Otherwise knobkit dev detects the tier from
whether your entry file ends in mount() or serve().
knobkit playground runs your app's normal dev server and wraps it in an editor + live preview: edit
on the left, see the running app update on the right (Vite HMR for mount, server restart for serve).
A file picker lets you switch between the project's source files (the editor re-highlights per
language); the preview reloads on any of them. It works on either tier, and edits round-trip to disk —
so an external edit (your editor, or an agent) flows back into the playground editor too. The
playground is itself a knobkit app (a code widget and a frame), running on --port (default 4317).
await convo.history(), await box.value() — pulled from the browser.convo.say(m), convo.append(token), out.set(v), log.push(line).returning an event from a handler (it's re-emitted, like a user action).{ type, payload }. widget.sent("hi") builds one.on(event, handler) registers a handler against a widget's event (convo.sent, go.clicked).setup(fn) runs once per session — in the browser on mount, per connection on serve — with a
live context, so async startup (load model weights, fetch a user's data) happens here. The page
renders first; setup is non-blocking.busy marks a transient working span on a widget — widget.busy(handler) wraps an async handler,
or widget.busyStart()/busyEnd() bracket one by hand (e.g. a setup() load). The widget shows a
thin indeterminate bar and drops its input events while busy. disable()/enable() is the persistent
version (dimmed).mount vs serve is the only thing that changes between in-browser and client/server — the
widgets, handlers, and methods are identical.mount("#root") | serve() | |
|---|---|---|
| State | in the browser | in the browser |
on(...) handlers | run in the browser | run on a stateless Node server |
| Transport | local function call | socket.io |
| Use when | everything fits client-side (incl. WebGPU models) | the handler needs the server (large models, secrets, native deps) |
Inputs: text, number, slider, dropdown, checkbox, checkboxGroup, radio, upload, mic,
webcam, chat, button.
Outputs: output (plain text or format: "markdown"), json, log, label, html, image,
gallery, annotatedImage (boxes/labels over an image), highlightedText (spans over text), audio,
video, file (download), progress, chart (bar/line/area), frame (embed a URL or a sandboxed
HTML document in an isolated iframe).
Both (editable or read-only): code (syntax-highlighted editor/viewer — language, editable,
wrap to soft-wrap long lines), table (data grid).
Custom: widget({ state, … }).
widgets is the widget tree. An array is a vertical stack; row, col, and grid are containers
that nest other widgets — composed with the widget objects themselves, no keys or strings:
import { knobkit, upload, dropdown, button, output, row, col } from "knobkit";
knobkit({ widgets: col(photo, row(size, go), caption) });
knobkit({ widgets: grid([a, b, c, d], { cols: 2 }) });
tabs and accordion are containers too — tabs([{ label, content }, …]),
accordion({ label, open }, …children). Containers are themselves widgets whose state is their
arrangement, so a handler can restructure the UI at runtime — panel.add(chart), panel.remove(sidebar).
Two independent axes, set once at authoring or flipped at runtime:
theme — "system" (default; follows the OS via prefers-color-scheme), "light", or "dark".density — "xs" | "sm" | "md" | "lg" | "xl" (default "md"), scaling spacing, control sizes,
radii, and type — xs for packed dashboards, xl for spacious forms.knobkit({ widgets: [...], theme: "dark", density: "sm" });
A third option, fill: true, drops the centered card for a full-bleed shell that fills the
viewport (a flex column: a slim title/description header, then the root layout stretches to fill) —
for split panes and dashboards. It's what knobkit playground uses for its editor/preview split.
Every widget renders from one set of CSS custom properties (--pu-bg, --pu-accent, --pu-gap,
--pu-radius, the --pu-series-* chart palette, …). Theme and density just remap those tokens, so a
single switch restyles the whole kit — including the code editor, table grid, and chart.
Build a switcher with the runtime setters (e.g. for a settings menu or the playground toggle):
import { setTheme, setDensity } from "knobkit";
setTheme("dark"); // or "light" / "system"
setDensity("xs");
Both are just attributes on the document root, and they inherit — so you can scope them: put
data-density="xs" on one grid and that panel goes compact while the rest of the page stays normal.
To rebrand, override the tokens in your own CSS (e.g. :root { --pu-accent: rebeccapurple }) or define
a named theme as a [data-theme="brand"] { … } block and pass theme: "brand".
In examples/ — each is a single
demo.tsx. Run one with pnpm -F knobkit-example-<name> dev.
Requires Node ≥ 22 and pnpm.
pnpm install
pnpm -F knobkit build # build the 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.
FAQs
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.
The npm package knobkit receives a total of 15 weekly downloads. As such, knobkit popularity was classified as not popular.
We found that knobkit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.