Skip to content

Non-QWERTY keyboards

Shoji defaults to Ctrl+Alt as the primary modifier. This works on US QWERTY keyboards, but on European layouts the Option (Alt) key produces special characters like @, {, }, ~, |, and accented letters. Holding Alt to navigate windows will type those characters instead.

This page provides ready-to-use keybinding presets for non-QWERTY layouts.

The problem

On QWERTZ (German, Swiss, Czech), AZERTY (French, Belgian), and Nordic layouts, Alt+key combinations produce essential characters:

LayoutAlt conflicts
QWERTZ@ { } ~ | [ ]
AZERTY@ # { } [ ] | ~
Nordic@ $ { } [ ] ~ |

Shoji’s defaults already use Ctrl+Alt (not bare Alt) to reduce conflicts, but some combinations still collide. The presets below replace Alt entirely.

QWERTZ preset (German, Swiss, Austrian)

Uses Ctrl+Cmd as the primary modifier. No Alt key involved:

hs.loadSpoon("Shoji")
local mods = { "ctrl", "cmd" }
local modsShift = { "ctrl", "cmd", "shift" }
spoon.Shoji:configure({
default_layout = "tall",
enabled_layouts = { "tall", "wide", "bsp", "monocle" },
})
spoon.Shoji:bindHotkeys({
-- Navigation
focus_left = { mods, "h" },
focus_right = { mods, "l" },
focus_up = { mods, "k" },
focus_down = { mods, "j" },
focus_next = { mods, "n" },
focus_prev = { mods, "p" },
focus_last = { mods, "tab" },
-- Swapping
swap_left = { modsShift, "h" },
swap_right = { modsShift, "l" },
swap_up = { modsShift, "k" },
swap_down = { modsShift, "j" },
swap_next = { modsShift, "n" },
swap_prev = { modsShift, "p" },
swap_main = { modsShift, "return" },
-- Layout
cycle_layout_forward = { mods, "space" },
cycle_layout_backward = { modsShift, "space" },
increase_main_ratio = { mods, "=" },
decrease_main_ratio = { mods, "-" },
increase_main_count = { mods, "," },
decrease_main_count = { mods, "." },
retile_space = { modsShift, "r" },
reset_space = { modsShift, "0" },
-- Window state
toggle_float = { modsShift, "t" },
toggle_zoom = { mods, "f" },
-- Help
toggle_cheatsheet = { modsShift, "/" },
open_command_palette = { mods, "/" },
-- Regions
send_to_next_region = { mods, "]" },
send_to_prev_region = { mods, "[" },
balance_regions = { modsShift, "b" },
})
spoon.Shoji:start()

AZERTY preset (French, Belgian)

Same approach. Uses Ctrl+Cmd:

local mods = { "ctrl", "cmd" }
local modsShift = { "ctrl", "cmd", "shift" }
spoon.Shoji:bindHotkeys({
focus_left = { mods, "h" },
focus_right = { mods, "l" },
focus_up = { mods, "k" },
focus_down = { mods, "j" },
swap_left = { modsShift, "h" },
swap_right = { modsShift, "l" },
swap_up = { modsShift, "k" },
swap_down = { modsShift, "j" },
cycle_layout_forward = { mods, "space" },
swap_main = { modsShift, "return" },
toggle_float = { modsShift, "t" },
toggle_zoom = { mods, "f" },
})

Hyper key preset (any layout)

Map Caps Lock to a “Hyper” key (Ctrl+Alt+Cmd+Shift) using Karabiner-Elements. Then bind Shoji to single keys under Hyper:

local hyper = { "ctrl", "alt", "cmd", "shift" }
spoon.Shoji:bindHotkeys({
focus_left = { hyper, "h" },
focus_right = { hyper, "l" },
focus_up = { hyper, "k" },
focus_down = { hyper, "j" },
swap_left = { hyper, "left" },
swap_right = { hyper, "right" },
swap_up = { hyper, "up" },
swap_down = { hyper, "down" },
cycle_layout_forward = { hyper, "space" },
swap_main = { hyper, "return" },
toggle_float = { hyper, "t" },
toggle_zoom = { hyper, "f" },
})

This works on every keyboard layout because Hyper is a unique modifier combination that no OS or app reserves.

Karabiner rule for Caps Lock to Hyper (add to ~/.config/karabiner/karabiner.json):

{
"manipulators": [
{
"from": { "key_code": "caps_lock" },
"to": [
{
"key_code": "left_shift",
"modifiers": ["left_command", "left_control", "left_option"]
}
],
"type": "basic"
}
]
}

Choosing a modifier

ModifierProsCons
Ctrl+CmdNo extra tools needed, no Alt conflictsMay conflict with some app shortcuts
Hyper (Caps Lock)Zero conflicts, works on any layoutRequires Karabiner-Elements
Ctrl+Alt (default)Works on QWERTY out of the boxConflicts with special chars on EU layouts

Pick the modifier that avoids conflicts with both your keyboard layout and the apps you use most. You only need to override the bindings that conflict — unspecified actions keep their defaults.