A modern, cross-platform keyboard layout editor for macOS .keylayout files and .bundle packages — an open-source alternative to Ukelele.
Build, edit, and inspect macOS keyboard layouts with a live, clickable keyboard —
no Xcode, no Apple Developer account, no Carbon APIs. Reads and writes the same
.keylayout XML and .bundle packages as Apple's tools and Ukelele.
Every screenshot below is split diagonally: light theme top-left, dark theme bottom-right.
🌐 Read this in: English · Deutsch · Français · Español · Italiano · Português · Nederlands · Polski · Українська · Русский · 日本語 · 简体中文 · 繁體中文 · 한국어 · हिन्दी · العربية · বাংলা · Bahasa Indonesia · اردو · Türkçe · Tiếng Việt · فارسی · தமிழ் · मराठी
🟢 New to GitHub, or not a developer? Read the plain-English Getting Started guide — how to download, install, and use Keymano with zero technical knowledge (including the macOS “unsigned app” first-launch step).
👉 Just want to try it right now? Open keymano.ys.contact — the full app in your browser, hosted by the maintainer. No install, no setup.
| Way | Who it's for | How |
|---|---|---|
| Download the desktop app | Most users (macOS / Windows / Linux) | Grab a build from Releases, or build from source (below). |
| Run in your browser | Try it instantly, no install | Use the hosted instance at keymano.ys.contact, or run pnpm dev and open http://localhost:1420 — the full UI runs the real Rust core compiled to WebAssembly. Self-host it with one Docker stack. |
| Build from source | Contributors, packagers | pnpm install && pnpm tauri dev — see Build from source. |
The desktop app is a small native binary (Tauri + Rust). The browser version is the exact same UI. In the browser, Open imports a real
.keylayoutor.bundleyou pick, Save/Export download a real.keylayout, and exporting a bundle downloads a.bundle.zip(unzip → drop into~/Library/Keyboard Layouts/). Only installing directly into the system Keyboard Layouts folder is desktop-only.
Keymano is an open-source, cross-platform alternative to Ukelele — the long-standing macOS keyboard-layout editor. Visual per-key editing, dead keys, modifier maps, and bundles on a modern stack you can run on macOS, Windows, Linux, or in the browser.
If you have ever searched for any of the following, this is for you:
- a Ukelele alternative or Ukelele replacement that runs on Windows or Linux,
- how to create a custom macOS keyboard layout or edit a
.keylayoutfile, - a keyboard layout editor for building dead keys, accents, and custom symbols,
- a way to open, inspect, or repair an existing
.keylayoutwithout a Mac, and export a macOS.bundlepackage from the browser.
Keymano reads and writes Apple's native formats, so layouts move freely between Keymano, Ukelele, and macOS itself:
.keylayout— Apple Keyboard Layout XML (theKeyboardLayout.dtdformat)..bundle— multi-layout keyboard packages (Info.plist,.strings, icons).
Comments, modifier maps, dead-key state machines, and Unicode/codepoint outputs are preserved through a parse → edit → serialize round-trip.
- Visual editor — click any key to set its output for any modifier combination and dead-key state; switch ANSI / ISO / JIS physical geometries.
- Modifiers — inspect the layout's real
keyMapSelectmap; latched (sticky) modifier layers for editing a whole layer at once. - Dead keys & actions — make-dead-key flow, terminators, and housekeeping (remove unused states/actions, add the special-key outputs macOS expects).
- Base-map inheritance — unlink a key to make it absolute, or relink it to inherit again.
- Editing aids — Quick Entry (type characters straight onto keys), find-by-output, select-by-code, copy / paste / swap, and undo / redo with named actions.
- Templates & system layouts — start from Standard (US) or a blank Unicode
layout; in the desktop app, open installed
.keylayout/.bundlefiles, install to and uninstall (move-to-Trash) from~/Library/Keyboard Layouts, with a live folder watch (macOS). - Export — PNG of the current view, a monochrome reference sheet across every modifier map, and a live XML preview with validation and one-click auto-repair.
- Polished app — recent files, light / dark / system themes, a selectable keycap font, a per-page guided Help tour, and a UI translated into 24 languages (including right-to-left scripts).
The interface ships in 24 languages, with key-parity and placeholder-integrity checks in CI:
English · Deutsch · Français · Español · Italiano · Português · Nederlands · Polski · Українська · Русский · 日本語 · 简体中文 · 繁體中文 · 한국어 · हिन्दी · العربية · বাংলা · Indonesia · اردو · Türkçe · Tiếng Việt · فارسی · தமிழ் · मराठी
Every tagged release is built by CI and published on the Releases page with bundles for each platform:
| Platform | File | Notes |
|---|---|---|
| macOS (Apple Silicon + Intel) | Keymano_*_universal.dmg |
Unsigned — see macOS first-run below |
| Windows (x86_64 / arm64) | .msi or .exe |
SmartScreen may warn; choose Run anyway |
| Linux (x86_64 / arm64) | .deb or .AppImage |
Keymano is not signed with an Apple Developer ID. Install from the .dmg
(drag Keymano to Applications). If macOS refuses to open the app, run this
once in Terminal:
xattr -d com.apple.quarantine /Applications/Keymano.appTypical message: “Apple could not verify…” — normal for free unsigned apps. Step-by-step help for non-developers: Getting Started — First launch on macOS.
The entire UI runs in a browser — ideal for a quick look or a hosted demo. It
runs the same Rust core as the desktop app, compiled to WebAssembly, so
parsing, serialization, validation, and modifier/dead-key resolution are
identical. Open imports a real .keylayout; Save/Export downloads a real
.keylayout; exporting a bundle downloads a <Name>.bundle.zip (unzip once →
drop the resulting .bundle into ~/Library/Keyboard Layouts/).
Browsers can't write into that folder directly, so on the web Install hands
you the file to place yourself; the desktop app does it in one click.
Browser limits: the web version cannot (1) import a .bundle directory
package directly, (2) install/uninstall layouts into ~/Library/Keyboard Layouts/, or (3) browse/watch installed system layouts. For those native
filesystem actions, use the desktop app. Everything else in the editor works in
the browser: edit .keylayout, validate/repair, preview XML, export
.keylayout, export .bundle.zip, PNG/reference-sheet export, undo/redo, dead
keys, and modifier maps.
pnpm install
pnpm dev
# open http://localhost:1420Prerequisites (all platforms): Rust (stable),
Node.js 20+, and pnpm (corepack enable).
Platform dependencies for the desktop build:
macOS
Xcode Command Line Tools (xcode-select --install). Nothing else — the system
WebView is built in.
pnpm install
pnpm tauri dev # run in development
pnpm tauri build --bundles app # produce Keymano.app (skips the .dmg step)Windows
Install the MSVC C++ Build Tools and WebView2 runtime (preinstalled on Windows 11). Then:
pnpm install
pnpm tauri dev
pnpm tauri build # produces an .msi / .exe installerLinux
Install WebKitGTK and friends (Debian/Ubuntu names shown):
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
pnpm install
pnpm tauri dev
pnpm tauri build # produces .deb / .AppImageThe web app and all tests run in containers (the native desktop build needs a system WebView and is built per-OS, not in Docker):
docker compose up web # dev UI -> http://localhost:1420
docker compose up web-prod # production build -> http://localhost:8080 (nginx)
docker compose run --rm check # eslint + typecheck
docker compose run --rm web-test # frontend tests (vitest)
docker compose run --rm core-test # Rust core + session testsA multi-arch image (amd64 + arm64) is published to the GitHub Container
Registry on every push to main (:latest + commit SHA) and on every v* tag
(:1.2.3, :1.2, :latest):
ghcr.io/ysalitrynskyi/keymano:latest
docker-compose.prod.yml runs that image behind a
Cloudflare Tunnel,
so nothing is exposed on the host — Cloudflare connects to the container over an
outbound-only tunnel. This is exactly how the hosted
keymano.ys.contact instance runs.
Portainer (recommended):
- Stacks → Add stack, paste
docker-compose.prod.yml. - Under Environment variables, set
CLOUDFLARE_TUNNEL_TOKEN(from the Zero Trust dashboard → Networks → Tunnels → your tunnel → install token). - Deploy the stack. The image is pulled from GHCR — no local build.
- In the tunnel's Public Hostnames, add a route:
keymano.ys.contact→http://keymano:80.
Plain Docker Compose:
cp .env.example .env # then set CLOUDFLARE_TUNNEL_TOKEN
docker compose -f docker-compose.prod.yml up -dThe hosted build is the in-browser app — the full UI running the real Rust core compiled to WebAssembly, so parsing/serialization match the desktop app.
- Start a layout. From the Welcome screen pick Standard (US), Basic (empty
Unicode), or open an existing
.keylayout(button or drag-and-drop). In the desktop app you can also open.bundlepackages; on macOS you can fork from an installed layout. - Edit keys. Click a key, then Edit (or double-click) to set its output. Toggle Shift / Option / Control / Command / Caps to edit each modifier layer. Use Quick Entry to type characters directly onto successive keys.
- Dead keys. Right-click a key → Make dead key, choose a state, set its terminator. Manage states and actions on the Dead Keys page.
- Validate. The XML & Validation page shows the generated
.keylayoutand flags anything that would stop macOS from loading it, with one-click auto-repair. - Save / install / export. Save as
.keylayoutor.bundle. On macOS, Install copies to~/Library/Keyboard Layouts/— then add it in System Settings → Keyboard → Input Sources (or log out and back in). Export a PNG or a monochrome reference sheet for printing.
macOS built-in layouts are sealed. Apple ships its bundled layouts (U.S., Russian, …) in a binary bundle that no app can read — Ukelele can't either. Keymano lists them so you can start a blank layout named after one, but to edit a real layout, open its
.keylayout/.bundlefile directly.
macOS says the app is damaged / from an unidentified developer
Keymano is unsigned (free, open-source; no paid Apple Developer ID). Install from
the .dmg into Applications, then in Terminal run:
xattr -d com.apple.quarantine /Applications/Keymano.appYou only need this once. Full walkthrough (including what the scary Gatekeeper message means): Getting Started — First launch on macOS.
Windows SmartScreen warns about the installer
Because the build is unsigned, SmartScreen may show “Windows protected your PC.” Click More info → Run anyway. The installer is the file published on the Releases page.
I installed a layout but it's not in my input menu (macOS)
After Install, add it in System Settings → Keyboard → Input Sources → Edit
→ +, then pick it. If it doesn't appear, log out and back in (or restart) —
macOS only re-scans ~/Library/Keyboard Layouts on login.
What's the difference between the browser and desktop versions?
Same UI and the same Rust core — the browser runs keylayout-core compiled
to WebAssembly, so parsing/serialization/validation match the desktop app byte
for byte. Browser limits are filesystem limits: it opens standalone
.keylayout files, but cannot import .bundle directory packages directly,
cannot browse/watch installed system layouts, and cannot write into the system
Keyboard Layouts folder. On the web, Install downloads the file for you to
place yourself — a standalone doc downloads as .keylayout, a bundle as
<Name>.bundle.zip (unzip first, then drop the .bundle into
~/Library/Keyboard Layouts/). The desktop app handles .bundle packages and
installs in one click.
Can I edit one of Apple's built-in layouts?
Not directly — Apple ships those as a sealed binary bundle no app can read
(Ukelele can't either). Start a new layout, or open any standalone
.keylayout / .bundle file you have.
How do I report a bug or ask for a feature?
Open an issue (see the step-by-step guide). For security problems use SECURITY.md, not a public issue.
crates/keylayout-core Pure Rust: model, parse, serialize, modifier resolution,
dead-key resolution, validation + repair, templates, ids,
special keys, bundles. No Tauri, fully unit-tested.
crates/keymano-session Tauri-free document session: open docs, undo/redo, edits.
crates/keymano-wasm wasm-bindgen wrapper over the session → the browser build.
src-tauri Thin Tauri command shell over the session (desktop).
src React + TypeScript frontend: keyboard, pages, ipc, i18n.
src/assets/keyboards ANSI / ISO / JIS geometry presets (data-driven JSON).
The Rust core is portable and Tauri-free, so the same parsing and serialization
logic powers the desktop app (over Tauri IPC) and the browser build (compiled to
WebAssembly) — one engine everywhere — as well as the tests.
Contributor docs: docs/DEVELOP.md. User guide:
docs/GETTING_STARTED.md.
Issues and pull requests are welcome. See CONTRIBUTING.md for the build/test workflow and conventions, and the Code of Conduct. Found a security issue? Please follow SECURITY.md — don't open a public issue.
Quick local checks before a PR:
pnpm lint && pnpm wasm:build && pnpm exec tsc -b && pnpm test
cargo test -p keylayout-core -p keymano-session
cargo clippy --workspace --all-targets -- -D warningsThe examples/ folder ships four Keymano-generated phonetic
Cyrillic .keylayout files (Ukrainian, Bulgarian, Serbian, Macedonian) to open
right away — File → Open… or drag one onto the window.
They're produced by the project's own serializer and verified in CI to parse,
validate, and expose each language's full alphabet. See
examples/README.md.
The desktop app collects nothing and runs entirely offline — no telemetry,
no analytics, no trackers. The hosted web app at
keymano.ys.contact uses Google Analytics
(anonymous page-view / usage stats; Google sets its cookies). Your .keylayout
work never leaves your browser — only standard analytics events are sent.
Analytics is opt-in per deployment via the GA_MEASUREMENT_ID environment
variable, so a self-hosted instance has none unless its operator turns it on,
and the desktop app has no analytics path at all. See
PRIVACY.md for the full statement.
Keymano is released under the Apache License 2.0. Third-party
runtime dependencies and their licenses are listed in
THIRD_PARTY_NOTICES.md. All original code;
format compatibility follows Apple's public KeyboardLayout.dtd. Layouts
edited in Keymano interchange with other tools that use the same Apple formats.
"Apple", "macOS", and "Ukelele" are trademarks of their respective owners;
Keymano is an independent project and is not affiliated with or endorsed by
Apple or SIL International.
🌐 README in other languages: Deutsch · Français · Español · Italiano · Português · Nederlands · Polski · Українська · Русский · 日本語 · 简体中文 · 繁體中文 · 한국어 · हिन्दी · العربية · বাংলা · Bahasa Indonesia · اردو · Türkçe · Tiếng Việt · فارسی · தமிழ் · मराठी



