A modern Vite-based keyboard configuration UI built with React and TypeScript.
The illustrated user manual, at keybard.svalboard.com/manual/, walks through connecting, editing, layers, behaviors, backups and the overlay. Its source is in docs/manual, with a printable PDF; that README covers regenerating the screenshots and the review record. The production build copies it to dist/manual/ (see build/manual.ts).
npm install # Install dependencies
npm run dev # Start development server
# → http://localhost:5173keybard-ng/
├── src/ # Vite + React + TypeScript
│ ├── constants/ # KeyMap constants
│ ├── components/ # React components
│ ├── contexts/ # KeyboardContext
│ ├── services/ # TypeScript keyboard services
│ │ ├── key.service.ts
│ │ ├── usb.ts
│ │ ├── utils.ts
│ │ └── keyboard.service.ts
│ └── types/ # TypeScript definitions
├── tests/ # Test suite
│ ├── services/ # Service layer tests
│ ├── contexts/ # React Context tests
│ ├── fixtures/ # Test data and mocks
│ └── mocks/ # USB and API mocks
└── dist/ # Build output (gitignored)
- React 19 with TypeScript
- Vite for fast dev server and builds
- js-lzma for board-provided compressed definitions
- WebHID API for USB keyboard communication
npm run dev # Start dev server
npm run build # Production build
npm run preview # Preview production buildKeyBard supports two ways to view keyboard configurations:
Click "Connect Keyboard" to connect to a Svalboard-QMK keyboard via WebHID.
Browser Support: Chrome (89+), Edge (89+), Opera (75+), Brave Not Supported: Firefox, Safari (WebHID unavailable)
Click "Load File" to load a .svil or .vil configuration file.
Supported formats:
.svil- Native Svalboard layout format (recommended). Legacy.viablefiles still load..vil- Vial-compatible format
File size limit: 1MB maximum
You can freely switch between connected keyboards and loaded files. The display always shows the most recent source. The "Loaded From" field indicates whether you're viewing a device or a file.
import { useKeyboard } from './contexts/KeyboardContext';
function MyComponent() {
const {
keyboard,
isConnected,
loadedFrom,
connect,
loadKeyboard,
loadFromFile
} = useKeyboard();
// Use keyboard services...
}SvilUSB- USB HID communicationKeyboardService- Keyboard operations (load, getKeyboardInfo, etc.)KeyService- Keycode parsing and stringifying (parse, stringify, define, etc.)- Utilities - Byte manipulation (LE16, BE16, etc.)
✅ TypeScript conversion of core Sval modules ✅ USB communication layer ✅ Board-definition decompression via js-lzma ✅ KEY utilities (keycode parsing, CODEMAP, KEYMAP, KEYALIASES) ✅ React Context provider ✅ Basic connection UI ✅ File loading (.svil and .vil configuration files)
The editor includes keymaps, macros, combos, tap dances, overrides, leaders and pointing settings. The Overlay item installs and connects the separate native Keybard Host overlay. See the companion README for setup.
The app has light and dark themes (Settings > General > Appearance: System, Light or Dark). Dark mode is the dark class on <html>, set before first paint by the inline script in src/index.html and kept in sync by src/components/ThemeSync.tsx. Helpers live in src/lib/theme.ts.
Chrome colours come from tokens defined in src/index.css (:root for light, .dark for dark). Each light value equals the literal it replaced, so light mode is unchanged.
| Utility | Light | Dark | Use for |
|---|---|---|---|
bg-kb-gray |
#f1f2f2 |
#141517 |
Page background, row borders |
border-kb-key-border |
#f1f2f2 |
#3a3d42 |
Key gap/outline (Key.tsx only) |
bg-kb-gray-medium |
#eaeae9 |
#26282b |
Raised/recessed panels, unselected chips |
border-kb-gray-border |
#a7a9ac |
#6b6f76 |
Strong borders, control outlines |
bg-kb-surface |
#ffffff |
#1d1e21 |
Panels and cards (instead of bg-white / border-white) |
text-kb-ink (fill-, border-, ring-) |
#000000 |
#f1f2f2 |
Chrome text, icons, logos, focus rings (instead of text-black etc.) |
bg-kb-active / text-kb-active-fg |
#000 / #fff |
#e8e9ea / #111214 |
Selected pill or chip |
bg-kb-popover |
#EEEEEE |
#33363b |
Colour-picker popovers |
The shadcn variables (--background, --foreground, --popover, --primary, --muted, --border, --input, --ring, ...) are also redefined under .dark, so shadcn primitives in src/components/ui theme themselves.
- Chrome uses a token or a
dark:partner. Never writebg-white,text-black,border-black,fill-black,ring-black,bg-[#hex]orfill="black"for chrome; use the tokens above. For agray-*/slate-*utility, put adark:class for the same property and variants in the same string literal:text-gray-500 dark:text-neutral-400,hover:bg-gray-100 dark:hover:bg-neutral-800. Never change the light class itself. - Key data is never themed. Layer colours (
src/utils/colors.ts), key face colours andheaderClassNamekey headers (e.g.bg-kb-sidebar-dark) look the same in both themes. Never putkb-activeor other theme tokens in aheaderClassName. - Printed output stays light.
PrintableKeymap*is not themed, andindex.cssforces a light page under@media print.
tests/theme/no-hardcoded-chrome-colors.test.ts enforces rules 1 and 2 on src/**/*.tsx as part of npm test. Justified exceptions (dead code, developer tools, data lookups) go in tests/theme/allowlist.ts with a reason.
# Run all tests
npm test
# Run with coverage report
npm run test:coverage
# Run in watch mode (during development)
npm run test:watch
# Open Vitest UI
npm run test:uiThis project maintains 90% code coverage across:
- ✅ Utility functions and byte manipulation
- ✅ KeyService (keycode parsing, custom keys, layers)
- ✅ USB communication layer (mocked WebHID API)
- ✅ KeyboardService (keyboard loading, keymap management)
- ✅ QMK settings service
- ✅ React Context state management
tests/
├── services/ # Service layer tests
├── contexts/ # React Context tests
├── fixtures/ # Test data and mocks
├── mocks/ # USB and API mocks
└── utils/ # Test utilities
Tests follow the AAA (Arrange-Act-Assert) pattern:
it('should update key at specific position', async () => {
// Arrange
const layer = 0, row = 1, col = 2;
const keymask = 0x0004; // KC_A
// Act
await keyboardService.updateKey(layer, row, col, keymask);
// Assert
expect(mockUSB.send).toHaveBeenCalledWith(/* ... */);
});Tests run automatically on:
- Pull requests (all commits)
- Pushes to main/master
- Coverage reports posted to PRs
See VITE_SETUP.md for detailed setup and development documentation.
The Overlay item in the left navigation panel opens a small dialog, as do /#overlay and /#trainer links. It offers the Keybard Host download and connects Keybard to a running Host, so the overlay refreshes after edits and uses Keybard's keyboard language. The overlay's settings live in Keybard Host's own menus.
The native Keybard Host serves this same web UI and runs its renderer as a transparent tray-managed desktop overlay. It has an isolated Windows runtime and read-only Svalboard device worker. Host mode supports live active/default layers, optional held-key feedback, remembered-board reconnect, and settings in its overlay and tray menus. Keybard keeps its standard WebHID connection flow; the native overlay uses a separate read-only client.