A modern, statically-hosted typing trainer built for custom keyboard layouts. Practice typing with your own keyboard layout and keymap, with support for ZMK keymaps and MonkeyType-style text content.
- ⌨️ Custom Keyboard Layouts - Load keyboard layout JSON files (via file upload or URL)
- 🗺️ ZMK Keymap Support - Parse ZMK
.keymapfiles with multiple layers - 📚 Flexible Text Content - Support for word lists and quotes in MonkeyType format
- 🎯 Real-time Feedback - Visual keyboard highlighting showing the next key to type
- 📊 Performance Metrics - Track WPM, accuracy, and errors in real-time
- 💾 URL Sharing - Load configurations via query parameters for easy sharing
- 🎨 Responsive UI - Dark theme with Tailwind CSS
- 📱 Client-side Only - Runs entirely in the browser, perfect for GitHub Pages
- Node.js 16+ (for development)
- npm or yarn
cd type-o-naut
npm installnpm run devThis will start a development server at http://localhost:5173.
npm run buildOutput will be in the dist/ directory.
On first load, the app comes with:
- Keyboard Layout: Ergonaut One S
- Keymap: FOCAL layer (a Colemak-based layout)
- Text: English 1K word list
Click the Settings button (⚙️) to open the Configuration Panel where you can:
- Upload a JSON file with keyboard layout definition
- Or provide a URL to fetch from
- Required format:
{
"id": "keyboard_id",
"name": "Keyboard Name",
"layouts": {
"LAYOUT": {
"layout": [
{"row": 0, "col": 0, "x": 0, "y": 0},
{"row": 0, "col": 1, "x": 1, "y": 0.32}
]
}
}
}- Upload a
.keymapfile - Or provide a URL to fetch from
- The app will parse all layers and allow you to select which one to practice on
- Example binding formats supported:
&kp KEY- Key press&mt MOD KEY- Mod-tap (modifier + key)< LAYER KEY- Layer-tap&mo LAYER- Momentary layer&none- Empty key
- Upload JSON in MonkeyType word list format:
{
"name": "word_list_name",
"words": ["word1", "word2", "word3"]
}- Or quotes format:
{
"language": "english",
"groups": [[0, 100], [101, 300]],
"quotes": [
{"text": "...", "source": "...", "length": 0, "id": 1}
]
}Share configurations via URL parameters:
https://your-domain/type-o-naut/?keyboardUrl=URL&keymapUrl=URL&textUrl=URL
Example:
https://your-domain/type-o-naut/?keyboardUrl=https://example.com/my_layout.json&keymapUrl=https://example.com/my_keymap.keymap&textUrl=https://example.com/words.json
The app converts ZMK keycodes to human-readable labels:
| ZMK Keycode | Display |
|---|---|
A-Z |
A-Z |
N0-N9 |
0-9 |
SPACE, SPC |
␣ |
ENTER, RET |
⏎ |
TAB |
⇥ |
BSPC |
⌫ |
DEL |
⌦ |
LEFT_SHIFT |
⇧ |
LEFT_CONTROL |
⌃ |
LEFT_ALT |
⌥ |
LEFT_GUI |
⌘ |
UP, DOWN, LEFT, RIGHT |
↑, ↓, ←, → |
Modifier combinations are displayed as MOD+KEY, e.g.:
&mt LEFT_SHIFT A→⇧+A&mt LEFT_CONTROL S→⌃+S
Layer-tap keys show both the layer and key:
< 1 SPACE→L1/␣
type-o-naut/
├── src/
│ ├── components/
│ │ ├── TypingTrainer.tsx # Main component with state management
│ │ ├── StatsDisplay.tsx # WPM, accuracy, errors display
│ │ ├── TextDisplay.tsx # Text rendering with feedback
│ │ ├── KeyboardDisplay.tsx # Keyboard visualization
│ │ └── ConfigPanel.tsx # Settings UI
│ ├── utils/
│ │ ├── zmkParser.ts # ZMK keymap parsing
│ │ ├── layoutValidator.ts # Keyboard layout validation
│ │ ├── textLoader.ts # Text content loading and validation
│ │ └── fileLoader.ts # File and URL loading utilities
│ ├── types/
│ │ └── index.ts # TypeScript type definitions
│ ├── App.tsx
│ ├── main.tsx
│ └── index.css
├── public/
│ └── defaults/
│ ├── ergonaut_one_s.json
│ ├── ergonaut_one_s.keymap
│ └── english_minimal.json
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Pages deployment
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
└── postcss.config.js
The app provides detailed error messages for:
- Invalid JSON - Malformed JSON files
- Missing required fields - Layout missing coordinates, keymaps without bindings
- Invalid bindings - Unrecognized ZMK keycodes
- Network errors - Failed to load from URLs
- Type validation - Wrong data structure detected
All errors are displayed in the Configuration Panel with helpful descriptions.
- Push to repository with the
mainormasterbranch - GitHub Actions automatically builds and deploys to GitHub Pages
- Access at
https://username.github.io/type-o-naut/
- In repository settings, enable GitHub Pages
- Set source to "Deploy from a branch"
- Select
gh-pagesbranch (created by Actions)
The workflow file (.github/workflows/deploy.yml) handles everything automatically.
Create new word list files in public/defaults/ and load them via URL parameters.
Edit src/index.css and tailwind.config.js to customize colors and layout.
Extend the ZMK_KEYCODE_MAP in src/utils/zmkParser.ts to support additional keycodes.
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Modern mobile browsers
- Bundle size: ~150KB (gzipped)
- No external API calls (except for user-provided URLs)
- Instant UI updates - 60 FPS keyboard highlighting
- Efficient re-renders - React with TypeScript
- Save results to localStorage
- Theme customization
- More ZMK binding types (combos, macros)
- Import from Keyboard Layout Editor (KLE)
- Multi-language support
- Typing statistics and history
Contributions are welcome! Please feel free to submit pull requests or open issues.
MIT License - feel free to use this for your own typing practice!
- Ensure the URL is accessible and CORS-enabled
- Check browser console for specific error messages
- Verify the
.keymapfile has validdefault_layersection - Check that layer definitions have proper
bindingsarrays
- Ensure layout JSON has all required key properties (
x,y) - Check that key positions don't overlap (or intentionally position them)
- Ensure JSON has either
wordsarray (word list) orquotesarray (quotes) - Word lists require
nameproperty, quotes requirelanguageproperty
- Inspired by MonkeyType typing test
- Keyboard visualization based on Keyboard Layout Editor
- ZMK parsing inspired by Keymap Editor and Keymap Drawer