A static site generator for keyboard layout tours. Define your keyboard's physical layout and key bindings in a couple of config files, and keyviz renders an interactive SVG diagram you can share or host anywhere.
pnpm install
pnpm dev # dev server at http://localhost:5173
pnpm build # output static site to build/
pnpm preview # preview the built siteCreate a directory under src/keyboards/<your-keyboard-name>/. The name becomes the URL path (e.g. src/keyboards/myboard/ → /myboard).
Describes the physical position of every key using ergogen points syntax.
units:
cx: 18
cy: 17
points:
zones:
matrix:
columns:
pinky:
ring:
key:
stagger: 4
# ...
rows:
bottom:
home:
top:
mirror:
ref: matrix_pinky_home
distance: 100See src/keyboards/paw/keys.yaml for a complete example with splay, stagger, and mirroring.
Set encoder: true on a key to draw it as a round knob; its binding is the encoder's push switch. A label on a key is shown in white below it (e.g. what turning the knob does), and a legend is shown on any layer where its binding has no tap legend of its own (e.g. a symbol for a &mo layer key).
Defines the legends shown on each keycap. Uses a simplified subset of ZMK binding syntax.
display-name: "Base"
bindings:
&kp Q < WIN W &ht SHIFT X ...
Binding types:
| Syntax | Behaviour | Rendered as |
|---|---|---|
&kp KEY |
Tap sends a keypress | Tap character, top ⅔ of keycap, charcoal |
< LAYER KEY |
Hold activates a layer; tap sends a keypress | Tap character (top ⅔) + layer name (bottom ⅓, pink) |
&mo LAYER |
Hold activates a layer | Layer name (bottom ⅓, pink) |
&ht MOD KEY |
Hold activates a modifier; tap sends a keypress | Tap character (top ⅔) + modifier name (bottom ⅓, orange) |
&trans |
Transparent — passes through to the layer below | Gray keycap, no legend |
&ok description |
OS/app command; description uses _ in place of spaces |
Blue description text, word-wrapped across lines |
&none |
Does nothing | Blank keycap |
&bt BT_SEL 0 (or &bt 0) |
Switch to Bluetooth channel | Bluetooth symbol + channel number |
&bt BT_CLR |
Clear Bluetooth pairing | Bluetooth symbol + "CLR" |
&out OUT_USB/OUT_BLE |
Select USB or BLE output | Blue "USB" / "BLE" |
&sys_reset, &bootloader |
Reset / enter bootloader | Blue "Reset" / "Boot loader" |
&os_sel MAC/WIN/LIN |
Select OS (affects modifier behaviour) | Apple / Windows / Linux icon |
any other &behavior … |
— | Blue behaviour name and parameters |
Every binding starts with &, so the bindings block of a ZMK .keymap can be pasted in as-is (see src/keyboards/vault/).
LAYER is a .layer file's name, uppercased. A _LAYER suffix is ignored, since ZMK needs one when a layer shares a keycode's name: < SPACE_LAYER DEL holds space.layer.
ZMK key names — single characters (letters, digits) render as-is. Common aliases like EXCL, AMPS, CARET, BSPC, RET, SPACE, UP etc. are mapped to their display characters. See src/lib/layer/keymap.ts for the full list.
Binding order matches the visual layout: for each zone (in YAML declaration order), rows are listed top-to-bottom, and within each row the non-mirrored (left) columns come first in YAML order, followed by the mirrored (right) columns in reverse YAML order (so they read left-to-right visually).
See src/keyboards/paw/ for complete examples of all binding types.
Combos are key combinations that trigger a binding when multiple physical keys are pressed simultaneously. They are defined in the same .layer file as bindings, after a combos: section.
combos:
esc:
description: "ESC"
key-positions: 11 12 13
bindings: &kp ESC
brac:
description: "Bracket (One-Shot)"
key-positions: 13 14
bindings: &sl BRAC
Each combo entry has:
| Field | Description |
|---|---|
description |
Text label shown in the diagram (quoted string) |
key-positions |
Space-separated list of key indices (0-based, matching binding order) |
bindings |
A single ZMK binding — same syntax as in the bindings: section |
Binding types for combos:
| Syntax | Behaviour |
|---|---|
&kp KEY |
Sends a keypress |
&sl LAYER |
One-shot layer activation (highlighted in pink, clickable in the diagram) |
&skq MOD |
One-shot modifier |
&caps_word |
Activates Caps Word |
Combos are rendered as arcs connecting the involved keys. &sl LAYER combos are clickable — mousedown switches to that layer just like an < binding.
See src/keyboards/paw/base.layer for a complete example.
Mousedown on a key with an < LAYER binding switches the display to that layer. The held key is highlighted in green. Releasing the mouse anywhere returns to the base layer.
Keys showing &trans in the active layer are rendered gray (they fall through to the layer below and don't do anything special on the current layer).
- Tap character (
&kp) — charcoal, top two-thirds of keycap - Hold modifier (
&ht) — orange, bottom third - Hold layer (
<) — pink, bottom third - Command (
&ok) — blue, centered and word-wrapped - Transparent (
&trans) — dark gray, no legend - Held key — green background, no legend
Colors are defined as CSS custom properties in src/lib/theme.css and can be changed there:
:root {
--color-key-bg: #f5f0e8; /* bone white keycap */
--color-legend-tap: #110e1f; /* charcoal */
--color-legend-hold: #ff811c; /* orange — hold modifier */
--color-legend-layer: #a63e6f; /* pink — layer tap */
--color-legend-command: #4b73b8; /* blue — &ok command */
--color-key-held: #40b07e; /* green — held/activator key */
--color-key-stroke: #d0c8b8; /* key border */
}src/
keyboards/
<name>/
keys.yaml ← ergogen points config (required)
base.layer ← ZMK-style key bindings (optional)
lib/
ergogen/ ← ergogen YAML parser → KeyPosition[]
layer/ ← .layer file parser and ZMK key display map
components/
KeyboardLayout.svelte ← SVG renderer
theme.css ← color custom properties
routes/
[keyboard]/ ← one prerendered page per keyboard
Since this site is fully static (@sveltejs/adapter-static), deploying to Vercel requires no special configuration.
pnpm build
npx vercel deploy --prebuilt build/-
Push your repo to GitHub (or GitLab / Bitbucket).
-
Go to vercel.com/new and import the repository.
-
Vercel will auto-detect SvelteKit. Override the build settings if needed:
Setting Value Framework Preset SvelteKit Build Command pnpm buildOutput Directory buildInstall Command pnpm install -
Click Deploy. Every push to the default branch redeploys automatically.
Note: the
build/directory is the static output — this is what Vercel serves. TheOutput Directorymust be set tobuild(not the SvelteKit default of.svelte-kit/output).
- SvelteKit 2 + Svelte 5 (runes), TypeScript
- Tailwind CSS v4
@sveltejs/adapter-static— fully prerendered, no server requiredjs-yamlfor YAML parsing