Configuring Home Row Mods with Karabiner-Elements
This note shows how I’ve configured home row mods (HRM) with Karabiner-Elements (KE). I developed this configuration to address shortcomings in solutions I’ve found on Internet, e.g., lost key presses or inability to press multiple modifiers simultaneously. It turns out, it is suprisingly non-trivial to implement such a popular mod as HRM.
Configuration
Here’s a snippet of the approach for configuring “a” as Ctrl and “s” as Command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
{
"description": "Home row mods (as) - control, command",
"manipulators": [
{
// If I press "a" and "s"…
"from": {
// With any modifier…
"modifiers": { "optional": ["any"] },
"simultaneous": [{ "key_code": "a" }, { "key_code": "s" }],
"simultaneous_options": { "key_down_order": "strict" }
},
// … and press something else quickly, then just output "a" and "s".
"to_delayed_action": {
"to_if_canceled": [{ "key_code": "a" }, { "key_code": "s" }],
"to_if_invoked": [{ "key_code": "vk_none" }]
},
// … and release, then just output "a" and "s".
"to_if_alone": [
{
// Do not run the delayed action. We are committed.
"halt": true,
"key_code": "a"
},
{ "key_code": "s" }
],
// … and hold, then just treat it as a modifier.
"to_if_held_down": [
{
// Do not run the delayed action. We are committed.
"halt": true,
"key_code": "left_control",
"modifiers": ["left_command"]
}
],
"type": "basic"
},
// Cover the case of pressing `sa`.
{
"from": {
"modifiers": { "optional": ["any"] },
"simultaneous": [{ "key_code": "s" }, { "key_code": "a" }],
"simultaneous_options": { "key_down_order": "strict" }
},
"to_delayed_action": {
"to_if_canceled": [{ "key_code": "s" }, { "key_code": "a" }],
"to_if_invoked": [{ "key_code": "vk_none" }]
},
"to_if_alone": [
{
"halt": true,
"key_code": "s"
},
{ "key_code": "a" }
],
"to_if_held_down": [
{
"halt": true,
"key_code": "left_control",
"modifiers": ["left_command"]
}
],
"type": "basic"
},
{
"from": {
"key_code": "a",
"modifiers": { "optional": ["any"] }
},
"to_if_alone": [
{
"halt": true,
"key_code": "a"
}
],
"to_if_held_down": [
{
"halt": true,
"key_code": "left_control"
}
],
// If another key is pressed, while doing a combo with "a", just output
// "a".
"to_delayed_action": {
"to_if_canceled": [{ "key_code": "a" }],
"to_if_invoked": [{ "key_code": "vk_none" }]
},
"type": "basic"
},
{
"from": {
"key_code": "s",
"modifiers": { "optional": ["any"] }
},
"to_if_alone": [
{
"halt": true,
"key_code": "s"
}
],
"to_if_held_down": [
{
"halt": true,
"key_code": "left_command"
}
],
"to_delayed_action": {
"to_if_canceled": [{ "key_code": "s" }],
"to_if_invoked": [{ "key_code": "vk_none" }]
},
"type": "basic"
}
]
}
I use this config with the following parameters:
to_if_alone_timeout_milliseconds
: 400to_if_held_down_threshold_milliseconds
: 110simultaneous_threshold_milliseconds
: 90
Why so complex?
One reason why this configuration is so complex is that KE doesn’t let us define HRM in the form of: “if ‘a’ is held, then it’s left control”. The biggest obstacle is that if you have a manipulator for “a”, then pressing “a,” and then “s” before the if_held
action triggers, the a-manipulator cancels, and we get nothing. We need to work with manipulators for simultaneous key presses, and we need to define them for every combination that we might encounter. We also need to work with delayed actions to cover the case when a non-home row key gets pressed while a home row key is held.
Limitations
One limitation is that you can’t dynamically remove modifiers. For example, if you hold “as” for Ctrl-Command and release “s,” then you just lost both modifiers, not just Command.