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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
|
# rift config
# Copy this file to ~/.config/rift/config.toml or $HOME~/.config/rift/config.toml
[settings]
# Animations
# - animate: master switch for all window animations
# - animation_duration: seconds per animation (>= 0.0, typical 0.15–0.35)
# - animation_fps: frames per second (0.0 = display refresh rate). 60–120 recommended.
# ease_in_circ, ease_out_circ, ease_in_out_circ
animate = false
animation_duration = 0.3
animation_fps = 100.0
# Space activation behavior
# - If true, spaces start inactive (rift does not manage windows there)
# and you must toggle activation via a keybind (toggle_space_activated).
# - If false, spaces are managed by default and you can disable specific ones.
# Default is true; uncomment to change.
#default_disable = false
# Mouse/Focus behavior
# - focus_follows_mouse: moving the mouse into a window focuses it
# - mouse_follows_focus: when focus changes, move the mouse to the focused window
# - mouse_hides_on_focus: hide the mouse cursor after focusing a window
focus_follows_mouse = true
mouse_follows_focus = true
mouse_hides_on_focus = true
# focus_follows_mouse_disable_hotkey temporarily suspends focus-follows-mouse while held
# e.g. "Fn"; omit or set to null to keep focus-follows-mouse always active
#focus_follows_mouse_disable_hotkey = "Fn"
# Prevent certain apps from stealing focus/causing workspace switches
# Provide bundle identifiers (e.g., "com.apple.Spotlight")
# Examples:
# "com.apple.dock", "com.apple.systemuiserver", "com.apple.SecurityAgent",
# "com.raycast.macos", "com.apple.Spotlight"
auto_focus_blacklist = []
# Run commands on start
#
# Tip: You can subscribe to rift events and trigger your own scripts. The command will
# receive environment variables with context:
# RIFT_EVENT_TYPE # "workspace_changed" | "windows_changed" | "window_title_changed"
# RIFT_WORKSPACE_ID # workspace id
# RIFT_WORKSPACE_NAME # workspace name
# RIFT_WORKSPACE_INDEX # workspace index (window_title_changed only)
# RIFT_WINDOW_COUNT # number of windows (windows_changed only)
# RIFT_WINDOWS # comma-separated window ids (windows_changed only)
# RIFT_WINDOW_ID # window id (window_title_changed only)
# RIFT_PREVIOUS_WINDOW_TITLE # previous title (window_title_changed only)
# RIFT_WINDOW_TITLE # new title (window_title_changed only)
# RIFT_EVENT_JSON # full JSON payload of the event
#
# Example (sketchybar integration):
# run_on_start = [
# "rift-cli subscribe cli --event workspace_changed --command sh --args -c --args 'sketchybar --trigger rift_workspace_changed RIFT_WORKSPACE_NAME=\"$RIFT_WORKSPACE_NAME\" RIFT_WORKSPACE_ID=\"$RIFT_WORKSPACE_ID\"'",
# "rift-cli subscribe cli --event windows_changed --command sh --args -c --args 'sketchybar --trigger rift_windows_changed RIFT_WORKSPACE_NAME=\"$RIFT_WORKSPACE_NAME\" RIFT_WINDOW_COUNT=\"$RIFT_WINDOW_COUNT\"'"
# ]
run_on_start = []
# Hot Reloading
# - If true, rift will watch the config file for changes and reload automatically.
# - If false, your config changes will only apply when restarting rift.
hot_reload = true
[settings.layout]
# Layout Types:
# - "traditional" (i3/sway-like containers)
# - "bsp" (binary space partitioning),
# - "stack" (single stacked container)
# - "master_stack" (master area + stack area)
# defaults to "traditional" if omitted
#
# This is the default layout mode if there is nothing else configured
# in the `workspace_rules` section below.
# You can set different layouts per workspace using `workspace_rules`.
mode = "bsp"
# these settings only apply when layout mode == "master_stack"
[settings.layout.master_stack]
# Fraction of space reserved for the master area (0.05..0.95)
master_ratio = 0.6
# Number of windows kept in the master area (>= 1)
master_count = 1
# Which side the master area occupies (left | right | top | bottom)
master_side = "left"
# Where new windows go when the master area is already full (master | stack | focused)
new_window_placement = "master"
# these settings only apply when layout mode == "scrolling"
[settings.layout.scrolling]
# width of the active column (0..1 of screen width)
column_width_ratio = 0.7
# min_column_width_ratio / max_column_width_ratio: clamp bounds used by resize commands
min_column_width_ratio = 0.3
max_column_width_ratio = 0.9
# Alignment for the focused column: "left", "center", or "right"
alignment = "center"
# Horizontal focus behavior:
# - "niri": reveal columns only when needed based on navigation direction
# (disrespects alignment setting above)
# - "anchored": always align focused column according to `alignment`
focus_navigation_style = "niri"
# the scrolling layout tends to function better without animations,
# so you can disable them if you like
# animate = false
[settings.layout.scrolling.gestures]
# Enable horizontal scroll gestures to switch columns
enabled = false
# Invert horizontal direction (swap left/right)
invert_horizontal = false
# Number of fingers required for gesture
fingers = 3
# If true, scrolling past the end of the strip will trigger a workspace switch
propagate_to_workspace_swipe = false
[settings.layout.stack]
# How much of each stacked window sticks out (in pixels)
# Set to 0.0 to have them directly on top of each other.
# Recommended: 30-50 for clear edges while keeping focus visible.
stack_offset = 40.0
# Default stacking orientation when creating a new stack.
# Options:
# - "perpendicular" (default): choose the perpendicular orientation to the parent layout
# - "same": use the same orientation as the parent layout
# - "horizontal"/"vertical": force a specific orientation
default_orientation = "perpendicular"
[settings.layout.gaps]
# Gap configuration
# - outer: space between windows and screen edges
# - inner: space between tiled windows
# - per_display: optional display-specific overrides keyed by display UUID.
# When present, the values in a per-display override replace the defaults
# (you may override only `outer`, only `inner`, or both for a display).
# Use the display's UUID as the key (for example, from system profiler or via rift CLI).
[settings.layout.gaps.outer]
top = 0
left = 0
bottom = 0
right = 0
[settings.layout.gaps.inner]
horizontal = 0
vertical = 0
# Example per-display overrides:
# Replace the quoted key with your display's UUID. Only specify values you want to override.
# [settings.layout.gaps.per_display."11111111-2222-3333-4444-555555555555".outer]
# top = 20
# left = 20
# bottom = 20
# right = 20
#
# [settings.layout.gaps.per_display."11111111-2222-3333-4444-555555555555".inner]
# horizontal = 8
# vertical = 8
[settings.ui.menu_bar]
# enable menu bar workspace indicators
enabled = true
# if enabled, it will show all workspaces including empty ones. disabled because this
# tends to take up too much room in the menubar and then is auto hidden by macos
show_empty = false
# choose between drawing every workspace or only the active workspace
mode = "all"
# when showing workspaces as labels ("index" or "name"), choose which label to render
active_label = "index"
# when displaying workspaces, choose whether to draw the layout preview or a set of labels
# options are "layout" or "label"
display_style = "layout"
[settings.ui.stack_line]
# experimental stack line indicator (defaults to off)
enabled = false
# in a horizontal stack where the line will be (top/bottom)
horiz_placement = "top"
# in a vertical stack where the line will be (left/right)
vert_placement = "left"
# how thick the indicator will be (in px)
thickness = 20.0
# spacing between the stack line and window edges (in px)
spacing = 1.0
# experimental mission control
[settings.ui.mission_control]
enabled = false
# enable fade animation when entering/exiting mission control
fade_enabled = false
# native macos mission control fade is about 180ms
fade_duration_ms = 180.0
# Trackpad gestures
[settings.gestures]
# Enable horizontal swipes to switch virtual workspaces
enabled = false
# Invert horizontal direction (swap next/prev)
invert_horizontal_swipe = false
# Maximum vertical travel allowed for the horizontal swipe to register.
# Accepts normalized fraction (0..1) or percent (0..100). Example: 0.4 or 40.0
swipe_vertical_tolerance = 0.4
# Skip empty workspaces when swiping
skip_empty = true
# Number of fingers required for swipe
fingers = 3
# Normalized horizontal distance (0..1) to trigger swipe for NSTouch-based detection
distance_pct = 0.08
# Haptic feedback on commit (generic | alignment | level_change)
haptics_enabled = true
haptic_pattern = "level_change"
# Window snapping / drag-swap behavior
[settings.window_snapping]
# Fraction threshold (0.0..1.0) specifying how much of the dragged window
# area must be overlapped by a candidate window in order to trigger a
# swap while dragging. Typical values are around 0.2–0.4.
# If you prefer more aggressive swapping, lower the value; increase it
# to require greater overlap before a swap occurs.
drag_swap_fraction = 0.3
[virtual_workspaces]
# Virtual workspaces
# - enabled: if false, rift behaves like a simple tiling WM with a single space
# - default_workspace_count: number of workspaces to create on startup (1–32)
# - auto_assign_windows: when true, new windows can be auto-assigned using app_rules
# - preserve_focus_per_workspace: remember last focused window per workspace
# - workspace_auto_back_and_forth: when enabled, if you try to switch to the same workspace
# that's already active, it will switch to the last workspace instead
# - reapply_app_rules_on_title_change: if true, app rules are re-evaluated when a window's title changes
enabled = true
default_workspace_count = 10
auto_assign_windows = true
preserve_focus_per_workspace = true
workspace_auto_back_and_forth = false
reapply_app_rules_on_title_change = false
# Workspace-specific rules
# - workspace: target workspace by index (integer) or name (string)
# - layout: layout mode to use ("traditional", "bsp", "stack", "master_stack", "scrolling")
# workspace_rules = [
# { workspace = 1, layout = "bsp" },
# { workspace = "second", layout = "scrolling" }
# ]
workspace_rules = []
# Default workspace to activate on startup (0-based index).
# If omitted, defaults to 0 (first workspace). Must be less than default_workspace_count.
# Uncomment to change:
# default_workspace = 0
# Workspace names (indexed order). If fewer than default_workspace_count are provided,
# remaining workspaces are named "Workspace X".
# Do not provide more names than default_workspace_count.
workspace_names = [
"first",
"second"
]
# App rules (automatic assignment)
# Define rules that match new windows and set properties (workspace, floating, etc).
#
# Matchable fields:
# - app_id : application bundle identifier (exact)
# - app_name : substring match against application name
# - title_regex : regular expression matched against the window title
# - title_substring: literal substring (case-sensitive) matched against the title
# - ax_role / ax_subrole: exact match against macOS Accessibility (AX) role/subrole
#
# Rule options:
# - workspace (integer, 0-based): target workspace index. If omitted or invalid, uses the active workspace.
# Alternatively, `workspace` may be a workspace name string to target by name.
# - floating (boolean): whether matched windows should float by default.
# - manage (boolean): whether Rift should manage the matching window. Set to false to ignore the window completely (default = true).
#
# Matching behavior (summary):
# 1. All rules that match a window are evaluated.
# 2. If multiple matching rules share the same non-empty `app_id`, the rule
# with the most specified conditions (the most non-empty fields) wins.
# If there's a tie, the earlier rule in the file wins.
# 3. If matching rules do NOT share the same `app_id`, the rule with the
# highest specificity (most non-empty fields) is selected; ties are broken by order.
# 4. Within a single rule, all specified fields are combined conjunctively:
# the rule matches only if every non-empty field matches the window.
#
# Tips:
# - To make exceptions for a specific app, place a more specific rule before
# a general `app_id` rule. Example:
# app_rules = [
# { app_id = "com.example.X", title_regex = "Dialog", floating = true },
# { app_id = "com.example.X", floating = false },
# ]
#
# - Use `title_substring` for simple, literal (case-sensitive) substring matches.
# Use `title_regex` for advanced patterns.
#
# Accessibility (AX) matching:
# - `ax_role` and `ax_subrole` match the exact AX values reported by macOS
# (e.g., "AXWindow", "AXDialog", "AXSystemDialog", "AXToolbar").
# - Useful for distinguishing dialogs, sheets, toolbars, etc., when bundle/name/title aren't enough.
#
# Validation:
# - `title_regex` must be a valid regular expression. Invalid regexes cause the rule
# to be ignored and a warning to be logged.
#
# Examples:
# - Float any window with "Preferences" in the title:
# app_rules = [
# { title_substring = "Preferences", floating = true },
# ]
#
# - Match by app name substring and workspace:
# app_rules = [
# { app_name = "Calendar", workspace = 2, floating = true },
# ]
#
# - Accessibility example: float dialog windows for a specific app:
# app_rules = [
# { app_id = "com.example.X", ax_subrole = "AXDialog", floating = true },
# ]
#
# By default there are no app rules; add or uncomment rules below as needed.
app_rules = []
# Modifier combinations that can be reused in key bindings
# Define common modifier combinations to avoid repetition.
# Example usage: with `comb1 = "Alt + Shift"`, you can write:
# "comb1 + H" = { move_node = "left" }
[modifier_combinations]
comb1 = "Alt + Shift"
[keys]
# Key binding syntax
# - Use quotes around the key: "Alt + Shift + H"
# - Supported modifiers: Alt, Ctrl, Shift, Meta (Meta = Command ⌘ on macOS)
# - Arrow keys can be written as ArrowUp/ArrowDown/ArrowLeft/ArrowRight, or just Up/Down/Left/Right
# - Spacing is flexible: "Alt+Down" and "Alt + Down" are equivalent
# - Key values map to commands (see list below)
# Commands
# - toggle_space_activated
# - next_workspace / prev_workspace
# - next_workspace = true|false (optional skip-empty override)
# - prev_workspace = true|false (optional skip-empty override)
# - switch_to_workspace = N
# - move_window_to_workspace = N / move_window_to_workspace = { workspace = N, window_id = 123 } (optional window id)
# - create_workspace
# - switch_to_last_workspace
# - set_workspace_layout = { mode = "traditional"|"bsp"|"stack"|"master_stack"|"scrolling" } (active workspace)
# - set_workspace_layout = { workspace = N, mode = "traditional"|"bsp"|"stack"|"master_stack"|"scrolling" }
# - next_window / prev_window (focus wraps when it reaches last window in current workspace)
# - ascend / descend
# - move_focus = "left"|"right"|"up"|"down"
# - move_node = "left"|"right"|"up"|"down"
# - join_window = "left"|"right"|"up"|"down"
# - toggle_stack / toggle_orientation / unjoin_windows
# - toggle_focus_floating / toggle_window_floating / toggle_fullscreen / toggle_fullscreen_within_gaps
# - resize_window_grow / resize_window_shrink / resize_window_by = 0.05
# - swap_windows = [123, 456]
# - exec = "command" | exec = ["cmd", "arg1", "..."]
# - move_mouse_to_display = "left"|"right"|"up"|"down"|N|"<display_uuid>"
# - move_window_to_display = { selector = "left"|"right"|"up"|"down"|N|"<display_uuid>", window_id = 123 }
# - close_window = { window_server_id = 123 }
# - focus_window = { window_id = 123, window_server_id = 456 }
# - show_mission_control_all / show_mission_control_current / dismiss_mission_control (this is rift's own mission control, not macOS's)
# the following commands *only* work when the master stack layout is active
# - adjust_master_ratio = 0.05 / adjust_master_count = 1
# - promote_to_master / swap_master_stack
# the following commands *only* work when the scrolling layout is active
# - scroll_strip = { delta = 0.5 }
# - snap_strip / center_selection
"Alt + Z" = "toggle_space_activated"
"Alt + H" = { move_focus = "left" }
"Alt + J" = { move_focus = "down" }
"Alt + K" = { move_focus = "up" }
"Alt + L" = { move_focus = "right" }
"comb1 + H" = { move_node = "left" }
"comb1 + J" = { move_node = "down" }
"comb1 + K" = { move_node = "up" }
"comb1 + L" = { move_node = "right" }
"Alt + 1" = { switch_to_workspace = 0 }
"Alt + 2" = { switch_to_workspace = 1 }
"Alt + 3" = { switch_to_workspace = 2 }
"Alt + 4" = { switch_to_workspace = 3 }
"Alt + 5" = { switch_to_workspace = 4 }
"Alt + 6" = { switch_to_workspace = 5 }
"Alt + 7" = { switch_to_workspace = 6 }
"Alt + 8" = { switch_to_workspace = 7 }
"Alt + 9" = { switch_to_workspace = 8 }
"Alt + 0" = { switch_to_workspace = 9 }
"comb1 + 1" = { move_window_to_workspace = 0 }
"comb1 + 2" = { move_window_to_workspace = 1 }
"comb1 + 3" = { move_window_to_workspace = 2 }
"comb1 + 4" = { move_window_to_workspace = 3 }
"comb1 + 5" = { move_window_to_workspace = 4 }
"comb1 + 6" = { move_window_to_workspace = 5 }
"comb1 + 7" = { move_window_to_workspace = 6 }
"comb1 + 8" = { move_window_to_workspace = 7 }
"comb1 + 9" = { move_window_to_workspace = 8 }
"comb1 + 0" = { move_window_to_workspace = 9 }
"Alt + Tab" = "switch_to_last_workspace"
# commands to change the layout engine of the active workspace
# "Alt + Ctrl + 1" = { set_workspace_layout = { mode = "traditional" } }
# "Alt + Ctrl + 2" = { set_workspace_layout = { mode = "bsp" } }
# "Alt + Ctrl + 3" = { set_workspace_layout = { mode = "master_stack" } }
# "Alt + Ctrl + 4" = { set_workspace_layout = { mode = "scrolling" } }
"Alt + Shift + Left" = { join_window = "left" }
"Alt + Shift + Right" = { join_window = "right" }
"Alt + Shift + Up" = { join_window = "up" }
"Alt + Shift + Down" = { join_window = "down" }
"Alt + Comma" = "toggle_stack"
"Alt + Slash" = "toggle_orientation"
"Alt + Ctrl + E" = "unjoin_windows"
"Alt + Shift + Space" = "toggle_window_floating"
"Alt + F" = "toggle_fullscreen"
"Alt + Shift + F" = "toggle_fullscreen_within_gaps"
"comb1 + Ctrl + Space" = "toggle_focus_floating" # briefly bring focus to floating window
# smartly resize windows
"Alt + Shift + Equal" = "resize_window_grow"
"Alt + Shift + Minus" = "resize_window_shrink"
# Move mouse cursor to display by index (0-based) or UUID
# Examples:
# "Alt + Shift + A" = { move_mouse_to_display = 0 }
# "Alt + Shift + S" = { move_mouse_to_display = 1 }
# "Alt + Shift + D" = { move_mouse_to_display = "e9a4f5a1-2f8b-45c1-9f4e-5a6b7c8d9e0f" }
# Focus another display (direction/index/uuid)
# "Alt + Shift + F" = { focus_display = "right" }
# Move a window to a display (by index, UUID, or direction)
# "Alt + Ctrl + Up" = { move_window_to_display = { selector = "up" } }
# "Alt + Ctrl + Down" = { move_window_to_display = { selector = "down" } }
# "Alt + Ctrl + Left" = { move_window_to_display = { selector = "left" } }
# "Alt + Ctrl + Right" = { move_window_to_display = { selector = "right" } }
# "Alt + Ctrl + A" = { move_window_to_display = { selector = 0 } }
# if mission control is enabled
# this will show an exploded view of the windows in the active workspace
# "Alt + Ctrl + Shift + M" = "show_mission_control_current"
# this will show the mission control view shown in the readme
# "Alt + Ctrl + M" = "show_mission_control_all"
"Alt + Enter" = { "exec" = ["/bin/bash", "-c", "open -a \"/System/Applications/Utilities/Terminal.app\""] }
"Alt + Shift + D" = "debug" # prints layout tree
"Alt + Ctrl + S" = "serialize"
"Alt + Ctrl + Q" = "save_and_exit"
|