The Settings window explains its own rows: a label, often a
caption, and on some rows a ? with a longer note. This guide
covers what those cannot say — how things interact, where a
setting lives, why a move was refused, and the files behind it.
Open Settings from the KiwiDesk menu in the menu bar, or press
⌘, while a KiwiDesk window is key. Shortcuts ▸ General
offers a bindable Open Settings row for a global key; it
ships unbound. The Settings window tiles into your layout like
any other window and answers the window shortcuts (float it with
toggle_floating, move it between spaces).
Out of the box, macOS lets Tab reach text fields and lists
only. Turn on System Settings ▸ Keyboard ▸ Keyboard
navigation and Tab reaches every control.
Opening an area from the keyboard puts focus on the page, so the
next Tab reaches its first control; opening it with the mouse
takes no focus, as anywhere on macOS. Shift-Tab reaches the
← Home chip; ⌘[ or Escape goes back. ⌘K puts the
cursor in the search field from anywhere in the window.
With keyboard navigation off, a control that cannot hold focus
hands it to the search field instead. VoiceOver is unaffected,
and every card and panel title is a heading for its rotor.
A slider takes focus and the arrow keys step it, in the
readout’s own step.
A segmented control is one Tab stop; ← / → move the choice.
Where a row has right-click moves (rename, export or delete a
palette; reorder spaces), press ⌃. (Control-Period) on
the focused row for the same menu.
A layout switched from the menu bar’s Layout submenu is
session-only: it does not rewrite the profile, and the menu
shows a “not saved to profile” subtitle while it drifts. Keep
Layout in Profile “‹name›” writes what is on screen, every
screen at once, into the active profile. A Settings Save does not
touch a temporary layout, which stays on screen through it; the
one exception is a Space whose layout you changed in Settings
→ Spaces, which wins on Save.
With more than one screen the submenu nests: All Screens
first, then a row per screen in desk order. A layout belongs to
a Space, so a screen’s row sets the layout of whatever Space is
showing there.
Check for Updates… asks now; KiwiDesk also checks on its own
in the background, and a found update shows as a dot on the
menu-bar icon and an Update Available… row, never a pop-up.
If you installed with Homebrew, KiwiDesk keeps itself up to
date and brew upgrade steps aside; to move an older copy onto a
version that can do this, run brew upgrade --cask kiwidesk
once.
KiwiDesk keeps your custom Lua in ~/.config/KiwiDesk/init.lua
and never edits it. Settings stores its own configuration in
~/.config/KiwiDesk/gui.json (global) and one JSON file per
saved profile.
Saving in Settings never rewrites init.lua.
Custom Lua that is not app rules, float rules, ignore rules,
keybindings or profile bindings lives alongside the visual
editor; a blue banner reads “Custom Lua detected”.
If init.lua declares managed vocabulary (app_rules,
float_rules, ignore_rules, KiwiDesk.bind, keybinding
definitions), Settings shows a raw Lua editor instead. Adopt
into the GUI imports them, comments the migrated settings out
as a backup, and keeps your custom Lua (event hooks such as the
sketchybar bridge) live.
Once gui.json exists, the visual editor owns tiling, and
hand-written set_gap_global calls stop applying on monitor
changes; persist custom tiling as a profile.
First launch with an existing init.lua: the default
gui.json is seeded as long as init.lua declares no managed
settings, so a file of event hooks boots GUI-managed. An
init.lua that sets tiling itself (KiwiDesk.set_*, rules, or
keybindings) stays Lua-owned, and Adopt into the GUI is
offered instead.
Start at login (General) reads and writes the real macOS
login item. Crash supervision is command-line only:
kiwidesk service start installs a helper that relaunches
KiwiDesk after a crash, never after a deliberate Quit (CLI
reference). While that service runs, Start at login
shows as on and stops being editable; kiwidesk service stop
gives the switch back.
General ▸ Advanced ▸ Export KiwiDesk Backup… writes one file
with your settings, every profile and your saved palettes;
Restore from Backup… puts it back. Not included: init.lua
and the remembered window arrangement. KiwiDesk keeps no backups
of its own; for continuous sync see The gui.json
File.
Restoring replaces, it does not merge, and what it replaces
goes to the Trash. On the way, the remembered window
arrangement on that Mac is forgotten, and KiwiDesk picks the
profile matching the screens connected here (a Desktop binding
still wins). Refused: a file that is not a KiwiDesk backup, one
from a newer KiwiDesk, one that would restore nothing, and
one carrying settings when this Mac’s settings come from
init.lua (profiles and palettes alone restore there). A
restore that skips a profile or a palette says so.
~/.config/KiwiDesk/gui.json holds the global base
configuration. On a fresh install it is created at first launch
with the default shortcuts; on a
hand-written setup, the first time you Save in Settings.
Keeping multiple Macs in sync. Symlink ~/.config/KiwiDesk
into an iCloud Drive or Dropbox folder. Machine-specific state
does not travel: grant Accessibility on each Mac, and expect
display layout and macOS Desktops to resolve against what is
connected there.
{
"spaces": [ "1", "2", "mail" ],
"app_rules": { "com.spotify.client": "music" },
"float_rules": [ "com.apple.calculator" ],
"ignore_rules": [ "eu.exelban.Stats" ],
"profile_bindings": {
"9C1F2A44-3B0E-4E7D-9A21-6D5C8B7E0143": {
"profile": "Developer", "desktop": 1
}
},
"layers": [
{ "name": "default", "bindings": [...] }
]
}
spaces: space ids (strings), in order.
app_rules: app bundle identifier → space id.
float_rules: bundle identifiers (optionally
bundle-id:title) whose windows never tile.
ignore_rules: bundle identifiers never tracked. No
Settings control; Settings preserves it on save.
profile_bindings: Desktop identifier → the profile that
Desktop selects on your main screen. The key is the identifier
KiwiDesk stamps into the Desktop itself, not its Mission
Control number; the number sits in desktop as a label, and an
optional display records the screen the Desktop was last seen
on. On a Mac where the stamp cannot be written, the key is the
number. A file in the older { "1": "Developer" } shape is
rewritten once on load. See A binding follows its Desktop, not
its number.
layers: keybinding layers, only one active at a time.
Each has a name (“default” for the main set), an optional
icon (SF Symbol name or emoji), and bindings: rows
of combo (e.g. "cmd+alt+left"), lua (the body
inside function() ... end), kind (“navigation”,
“application”, or “custom”) and label.
A hand-edited layers list is normalized on load: empty names
are dropped, a duplicated name keeps its first entry, default
always exists and sits first, and an icon on default is
removed.
This key was called "modes" in earlier pre-release builds. A
file still using the old name loads as no shortcuts at all
and the next Save writes that emptiness back, so run this
before opening Settings:
Repeat it for any file in ~/.config/KiwiDesk/profiles/ with a
keybinding override, and rename KiwiDesk.define_mode and
switch_mode in a hand-written init.lua. Or Reset all
settings… trashes gui.json and reseeds the defaults.
To reset the app to what init.lua declares, delete gui.json.
Do not import one from an untrusted source: custom Lua in
keybindings runs on every reload.
gui.json (global), shared by every profile: the base
keyboard shortcuts, app rules, float rules, ignore rules, and
Desktop → profile bindings.
Each profile’s JSON, applied only while that profile is
active: which spaces exist and their order, layout mode, gaps
and per-layout / per-space tuning, space-to-monitor pins, the
Main role and the fallback space.
The General section leaves the grid while you edit a stored
profile without switching to it.
Two hybrids. Shortcuts and app rules are global, but a profile
can carry a sparse override of its own: shortcuts can add or
change specific bindings (Shortcut Layers),
and app rules can pin an app to a different space or un-pin it
(Per-Profile Space Assignments).
When profiles share a space name, one base rule is enough; when
their space sets are disjoint (Work {1, 2, 3} vs Home {media, games}), give each profile its own override.
Stack — the resize shortcuts are focus-aware: the split
axis grows whichever zone holds the focus, the zone’s own axis
grows the focused window’s share, and that share is
session-only. A master zone lined up along the split axis has
no reachable shares, which is the out-of-the-box case once the
master count exceeds one; switch the orientation to vertical
for individually resizable masters (Accepted
limitations).
Scrolling — its focus animation and duration live here,
not in Colors & Animations.
Track — the track shortcuts sit in Shortcuts ▸ Move
windows. Previous is the column to the left (or the row
above), next the column to the right (or the row below),
whichever way the axis runs.
Track sizes and in-track shares are session-only.
The track shortcuts sit behind “Move windows in the track
layout”, open from the start once you have a Track Space or a
track shortcut bound.
Floating — switching a space to Floating with any window
partly or fully off the screen (a scrolled-out column, a
parked Monocle window) or piled behind another (a Monocle
stack) arranges the space’s windows in the grid Behavior ▸
On quit uses; with everything already reachable, nothing
moves.
A few resize behaviors are accepted limitations, not bugs —
the inner window of a nested BSP pair not growing, or a stack
window’s mouse height-drag snapping back. See
Accepted limitations.
A space’s override editor (from its Customize… cell in
Spaces) edits the layout the space currently uses. Each row’s
Override checkbox, unchecked, inherits the Layout Defaults
value; checked, the control appears seeded with the current
value. A field greyed by a switch on another page (Grid’s
auto-size, Track’s auto limit) says so and links Layout
Defaults.
Changing a space’s layout never deletes overrides; each layout
keeps its own, and a Saved for N other layouts card at the
foot lists them. Reset All Layout Overrides there clears
every layout’s, after a confirmation.
Drag a space chip onto a display to pin it there; drag it onto
the dashed Follows main display tray to give it the Main
role, which moves with whichever display is main when you dock
and undock. Outlined chips are placed automatically; a filled
chip is yours. Every chip is also a menu, which is the keyboard
route (Using Settings from the
Keyboard).
KiwiDesk recognises a display by name and resolution, so two of
the same model at the same resolution are one identity and a
space pinned to one may open on either. A space pinned to an
absent monitor gets its own card below the picture with Back
to automatic placement.
The focused monitor is the one you last clicked — a window or
the bare wallpaper. A new window, and a global sticky window,
appear on that monitor’s space. Clicking the menu bar or the Dock
does not move focus.
Width and Corners at the top set the focus ring, the
drag ghost and the drop zone together. Keep gaps at least twice
the width so two neighbouring rings do not touch. Each stroke’s
own width, each overlay’s alignment and the drag radius are
Lua-only and never clamped against each other; design
decisions has why the GUI offers no switch,
the Lua reference the verbs. A radius set
from Lua shows as Rounded and keeps its value; if the ring
and the overlays disagree, neither segment is selected.
The ring’s colours are in Advanced Colors ▸ Border colors,
dimmed while the matching switch here is off.
What gets no border. Launcher and panel overlays (Spotlight,
Raycast, Alfred): never managed, never in a bar. Windows in
native (green-button) fullscreen: macOS moves such a window off
the Desktop, so the bars hide there, no layout pass or focus
raise targets it, and its space tiles as if it were away; it
keeps its slot and tiles back when it leaves fullscreen.
The pills. Focusing or swapping toward an edge with no window
there bounces the ring; the window never moves. A refused resize
bounces and shows a frosted pill:
Minimum window size reached — the window can shrink no
further than the configured minimum.
Neighboring window at its minimum size — growing stopped
where a neighbour would drop below its minimum; the neighbour
marks itself.
It also shows once, without a press, when a window arriving in
a split cannot fit beside a neighbour already at its minimum.
On a scrolling space, growing stops at a maximum the app
enforces (System Settings will not grow past its own width).
Where the limit is the app’s own — a minimum or maximum the
app enforces, which KiwiDesk learns — the pill says so instead:
This app won’t go smaller, Neighboring app won’t go smaller,
This app won’t go bigger. Lowering the configured minimum does
not move those.
Running out of screen is a silent stop. Under a held resize
shortcut, a refusal shows its pill once and ends the glide.
Floating windows join directional focus as a second tier: when
no tiled window lies in the pressed direction, focus reaches a
floating window that way.
Releasing a dragged window over another’s slot on the same
display swaps the two. Dragging onto another display moves it
there, live: once the cursor settles on the other display, its
windows slide apart to open a slot, and pulling the cursor back
moves the window home. Releasing outside every slot on your own
display snaps the window back. Floating windows show no overlay
and cannot be dropped onto a tiled slot; use make tiled first
(Accepted limitations).
A sticky window stays visible on every space. Two scopes,
both shortcuts under Size & float (stickiness is per window;
there is no app-rule list):
Toggle sticky everywhere — every space of every monitor
(∞ mark).
Toggle sticky on this screen — every space of the one
monitor it lives on (📌 mark). Moving it to a space on another
monitor re-homes it there.
Moving a ∞ window anywhere, or a 📌 window to another space on
the same monitor, is refused with a brief pill. On a single
monitor the two are identical. The flag survives closing and
reopening the window and is independent of floating: a floating
sticky window keeps its frame everywhere; a tiled one tiles into
every space’s layout near where it sits on its home space, keeps
a fully visible slot on a crowded space, and can be reordered or
mouse-resized only on its home space — elsewhere the gesture
snaps back and the mark expands into a pill naming the home
space. The same pill appears when you drag another window onto
its slot.
Where macOS supports it, sticky reaches across macOS
Desktops: switch Desktops and sticky windows come along with
the screen they are on. Stay visible across Desktops (beside
the mark toggle) switches it and appears only on a macOS that can
drive Desktops; override_sticky_reach in Lua pins a single
window the other way. Mission Control shows a sticky window on
one Desktop at a time.
A ∞ window follows the space you focus onto its screen; when
that space is a floating-mode space on another screen, KiwiDesk
moves the window there at its last size, scaled to the screen.
The refusal pills ride the sticky mark: with Show mark on
sticky windows off, a refused move fails silently. The Space
Bar’s badge shows which windows are sticky either way. (Lua:
sticky.set_mark, space_bar.set_sticky_badge,
sticky.set_color,
floating.set_color.)
The App Bar renders only in Monocle and Scrolling; its
card has no on/off row, the two Show it in switches are its
visibility. Drag an item to reorder the windows;
a grouped item expands into its members on click. Styling it
differently per layout is Lua-only: every app_bar.* field has a
monocle.set_app_bar_* / scroll.set_app_bar_* twin (Per-layout
App Bar overrides).
Liquid Glass is one switch for both bars and the shortcuts
panel (Colours & Animations), tinted by each bar’s Fill; on
macOS before 26 the Boxed or Plain shape draws underneath.
On by default, on all three surfaces. While macOS’s Reduce
transparency (System Settings ▸ Accessibility ▸ Display) is on,
the Boxed or Plain shape draws instead, with its Fill fully
opaque, and the switch stays as you set it.
One bar per display, listing that display’s Spaces in profile
order. Click a Space to switch to it; the glyphs are
informational.
While a shortcut layer other than default is active, its icon
— or two letters of its name when it has none — leads the bar,
ahead of the Spaces (Shortcut Layers).
The bar shows the Desktop you are looking at: a window on a macOS
Desktop you are not looking at is not listed, and Hide empty
Spaces hides a Space holding only those. Open or Focus still
reaches such a window and switches Desktops to it.
A sticky window’s glyph is listed under one Space only, the one
it renders on: a ∞ window under the Space you are focused on,
a 📌 window under the current Space of its own screen.
Mark
Where it sits
Means
∞ mark
On the window, top-right corner
Global sticky — every Space of every monitor
📌 mark
On the window, top-right corner
Display sticky — every Space of the one monitor it lives on
Badge, glyph’s top-left
Space Bar
That window (or one in the group) is sticky
Badge, glyph’s bottom-left
Space Bar
That window is floating
+n / count badge, glyph’s top-right
Space Bar
How many windows a grouped glyph holds
Floating has no on-window mark. The badges have no Settings
toggle; Lua hides them with space_bar.set_sticky_badge(false).
Drag a window onto a Space to move it there:
Flick and drop — release before the ring fills; the window
jumps there and you stay.
Hold to place — pause for the Spring delay; the view
springs to that Space with the window in its live layout, so
you can drop it exactly where you want. Move the cursor off
the item before the ring completes to cancel. Under Reduce
Motion the ring does not sweep: it stays away for the first
half-second, then appears whole for the rest of the hold.
Dropping onto the Space a window is already on does nothing.
While dragging, hold over a chevron to autoscroll a bar that
overflows.
Lua-only (enable_wake_restore, set_wake_restore_delay in the
Lua reference): after sleep or screen unlock,
KiwiDesk restores the arrangement captured when the Mac went to
rest (on by default, after a 1500 ms delay) and puts focus back
on the window you were in. A wake restore is skipped when the
display set changed during sleep; the monitor-change profile
switch takes over. If a restore leaves things wrong, General ▸
Advanced ▸ Discard Saved Window Arrangement clears it.
A Desktop binding fires only when the bound profile is saved for
the connected screen count (rung 1 below).
The card answers for your machine now — “Right now: 2 screens →
Desk (these exact monitors)” — naming which rung resolved it.
The rungs, in order:
A Desktop binding on the Desktop your main screen is on
(macOS Desktops), when
the bound profile is saved for this many screens. For any
other count the binding stands aside and the rungs below
decide.
An exact monitor match — these exact displays. It stops
matching the moment you swap one out, unless you saved a set
for the new hardware too.
The default for this screen count, whatever monitors are
plugged in. The first profile saved for a count becomes its
default; make default moves it.
A built-in Standard for that screen count, or a line
saying nothing matches.
A new hardware combination therefore uses the Standard and marks
the profile dirty until you Save on it; a profile saved for two
different monitors sorts after your exact matches but before
one-screen profiles.
The dropdown at the top of any section picks what your edits
target. Live (currently loaded) edits the running, global
config; every saved profile lists below it, one row each.
Live: saving adopts your changes into the loaded profile.
A saved profile, without switching: Home becomes
profile-scoped, the General card leaves the grid, and Save
writes to that profile’s JSON. Shortcuts and App Rules enter
override mode and edit only what the profile changes.
The loaded profile’s own row: saving re-applies the
profile in place, no switch. This row edits the profile’s
stored overrides (its app rules and shortcut layers); Live
edits the global base every profile inherits. For gaps,
layouts and bars the two rows save the same thing.
Saving a stored profile hot-reloads the running layout only if
that profile is on screen; otherwise the change waits until it
next loads. Save a copy… while editing a stored profile
duplicates it with your pending edits and its monitor sets, even
for absent hardware, without touching the running layout.
Save writes to the current target and adds or refreshes the
connected monitor set; it is greyed when the connected screen
count differs from the profile’s. A screen setup the profile has
no set for is itself an unsaved change, listed as a Screens
row. On a temporary layout or a built-in Standard the slot reads
Save as New Profile….
While management is paused (Accessibility off), KiwiDesk
detects no displays, so any save that captures the live monitor
set is unavailable. Shortcuts, app rules, float and ignore rules,
the space list and Desktop bindings carry no monitor set, so
Save still writes gui.json for them and keeps counting the
layout edits until you grant access.
Neither live save carries a keybinding override: to give a
profile its own shortcuts, pick it in the banner and edit its
Shortcuts section.
One workflow layout per screen count is the Standard that
resolves silently when no saved profile matches; Starter is
derived from the screens you have (Your first
run) and is offered for that count alone. Where
a preset does not name a layout for a space, the space takes the
layout its screen suits. Applying a preset saves it as a real,
editable profile; presets cannot be deleted.
The title match is case-sensitive, and “Info” also catches
“Information”; the live window list under the chips shows what
the rule as written would float. Dialogs, sheets and
picture-in-picture windows float without a rule, as do windows of
apps that remain accessory processes. For an app you have not
installed, app_rules in Lua takes bundle identifiers (Finding
a bundle identifier).
Apps with macOS native tabs (Finder, Terminal, Ghostty) are
one tile per window that follows the active tab. Tabs cannot be
split into separate tiles.
An ignore rule makes KiwiDesk treat every window of an app as
nonexistent — no space assignment, no window events — for HUDs,
menu-bar utilities, and apps that misbehave when AX-tracked.
There is no Settings control: add bundle ids to ignore_rules = { ... } in init.lua, or to the root ignore_rules array in
gui.json (ignore_rules). An
ignore rule also opts a misbehaving tabbed app out.
While you edit a stored profile, App Rules enters override mode:
dimmed facets are inherited, changing one overrides it for this
profile, and deleting a row removes inherited rules there. The
profile stores a sparse diff: app_rules maps apps to spaces,
while float_rules and ignore_rules are objects whose true
entries add rules and null entries remove inherited ones:
{
"app_rules": { "com.apple.mail": null },
"float_rules": {
"com.apple.calculator": null,
"com.apple.finder:Get Info": true
},
"ignore_rules": {
"eu.exelban.Stats": true,
"com.example.inherited": null
}
}
An absent entry inherits. Effective Ignore applies last as the
hard gate. Ignore has no GUI control; Settings preserves the
hidden object across saves, copies and renames.
A fresh install seeds a setup chosen for the screens you have.
Every screen opens in scrolling, except the smallest, which
opens in monocle. The slot is just under half the screen; on an
ultrawide main screen it is 30%, a profile-wide value read from
the main screen.
Your screen
Gets, best first
Laptop (under 1900 pt wide)
scrolling · monocle
2K / 4K desktop (1900–3000 pt)
grid · stack · bsp · scrolling
Ultrawide (3000 pt +, or wider than 2.1:1)
track · grid · stack
Pivoted (taller than wide)
stack · grid · monocle
Screens are measured in points, so a 5K 27” and a 1440p 27” get
the same answer. Every setup gets exactly one Floating space, on
the largest screen. The total is 3 spaces for one screen, 5 for
two, 7 for three, then 8, 9 and one more per screen up to ten,
each screen’s share proportional to its width. “Smallest” is
read from width alone, so a 27” beside an ultrawide opens in
monocle.
The tuning follows the main screen: a laptop main gets 6 pt
gaps, an ultrawide two stack masters and a larger minimum window
size, a pivoted one the stack at the bottom and scrolling
vertical.
While you are still on the Starter layout, connecting or removing
a monitor re-derives it and the ⌃⌥N space shortcuts extend to
new spaces (up to ten). It is saved as an ordinary profile named
Starter, and the same setup is always available as the
Starter preset.
⌃⌥ moves your focus, ⇧ sends the window to a space, ⌘
swaps it or sends it and follows; resizing has its own layer,
⌥⌘. Each space digit is bound by name and follows a rename;
spaces past the tenth ship without a digit. The set is seeded
only while no shortcut is bound anywhere, and an install that
already has shortcuts picks up a later default through Restore
Defaults…, which keeps the shortcuts you made yourself except
one sitting on a key a default needs; the confirmation counts
those.
Three things can claim a chord, and they do not lose it the same
way:
macOS wins outright: when a combo is one of its switched-on
shortcuts, the row never fires.
KiwiDesk wins against an app’s menu shortcut; the app loses
the accelerator but keeps the menu item. A shortcut with no
menu behind it (⌥⌘ arrows are next/previous tab in most
browsers and terminals) costs the capability outright.
⌃⌥ is the quiet corner macOS and most apps leave alone. ⌥
alone types characters that vary by layout (⌥L is ¬ on a US
keyboard, @ on a German one); adding ⌃ or ⌘ stops that.
Arrows and digits keep their place on every layout; letters do
not.
The ⚠️ on a row means it duplicates another row in the same
layer or collides with a macOS shortcut. Those two are the
whole of it: KiwiDesk cannot see what other apps have bound, so
the safest test of a chord is to bind it and open the app you
would miss it in. A collision with a switched-off macOS shortcut
(Zoom, Invert Colors) keeps the ⚠️ but does not count toward the
banner. A collision with a shortcut every app carries (⌘W, ⌘Q,
⌘H, ⌘M) means your row works and every app loses that item while
it is bound.
The recorder suspends your KiwiDesk shortcuts while it is
open, so a combo already bound to a window action can be
tested. On the live target a recording, a clear or a deleted row
takes effect at once, before Save; Revert restores the saved
ones, also live.
Keys: letters (a–z), digits (0–9), arrows, home, end,
pageup, pagedown, space, return, tab, escape,
f1–f12, and punctuation by symbol or name:
;/semicolon, ,/comma, ./period, //slash,
\/backslash, -/minus, =/equal, [/leftbracket,
]/rightbracket, `/grave/backtick,
'/quote/apostrophe.
The numeric keypad’s digits are the same keys as the number row.
Its other keys are separate: keypadplus, keypadminus,
keypadmultiply, keypaddivide, keypaddecimal,
keypadequals, keypadenter, keypadclear. A third-party PC
keypad sends digits only while Num Lock is on.
A combo is one set of modifiers plus exactly one key; multi-key
chords like cmd+j+k are not supported — use layers. KiwiDesk
binds the physical key, so switching input sources changes what
the caps print, not which key fires.
macOS Desktop rows (under Focus and Move windows) are one
per Desktop you have, on every screen, and ship unbound. They
appear only where macOS exposes the window-management bridge
(macOS Desktops). A bound
row whose Desktop leaves with its screen stays in place,
dimmed, and works again when the screen is back; a Desktop
shortcut for an absent Desktop does nothing, while a Space
shortcut for an absent Space still works: it recreates the
Space and switches to it. Each row sends the window to the Desktop and nothing
more; naming the Space it lands in is a Lua/CLI argument,
move_to_desktop(3, "mail") (Lua
reference).
Resize — a window outside tiling (floated by you, or in a
Floating space) resizes its own frame in any layout. Held, a
resize shortcut glides: the first press is one step; after
your Mac’s key-repeat delay the window resizes smoothly, faster
over the next couple of seconds, scaled to your resize step.
Only resize glides. A floating window grows around its centre;
an edge against the screen edge or a bar stays put and the
whole step goes to the other side. Floats are held clear of a
bar and the screen edge by the focus ring’s width.
Applications — Open or Focus pulls a running instance
into the current space, or launches it; pressing again while
its window is focused cycles the app’s other windows, other
Desktops included; with nothing open anywhere it restores one
minimized window. Add the same app twice to bind one shortcut
per behaviour.
If init.lua holds custom keybindings, Import from
init.lua… (in the Shortcuts header) reads them for review before
you Save; each binding must be an inline function() … end on
one line. Adopt into the GUI imports the whole file’s managed
settings and keeps your custom Lua live.
The + beside the layer chips defines a layer: a name, an
optional icon, and bindings that shadow the base
shortcuts while it is active. Every layer gets its own ⌃⌥K
row. Switch layers with KiwiDesk.switch_layer (Lua
reference). Editing a stored profile puts
Shortcuts in override mode: dimmed rows are inherited, an edited
row overrides for that profile only, and only those rows are
stored in its JSON.
Profiles ▸ Profiles per macOS Desktop lists the Desktops on
your main screen by their Mission Control number, counted
across every screen, so they need not start at 1. A Desktop you
bound that is not on your main screen carries a not on main
screen badge and cannot fire until a screen change makes it
the main screen’s; a Desktop that is not there at all carries
not present (Accepted
limitations). A binding stays on the
Desktop you gave it when Mission Control renumbers — A binding
follows its Desktop, not its
number.
A binding fires when its Desktop becomes current on your main
screen (the one with the menu bar) and its profile fits your
screen count (Which Profile Loads). With
“Displays have separate Spaces” on, macOS’s default, each screen
switches on its own: a swipe on the main screen switches
profiles, a swipe on a secondary never does. The rows are greyed
while you edit a stored profile: bindings are global.
flowchart TD
S["Your main screen switches<br/>to a macOS Desktop"] --> B{"A profile bound<br/>to this Desktop,<br/>saved for this many screens?"}
B -->|"Yes"| A["That profile activates —<br/>its layout, gaps, and rules"]
B -->|"No binding, or<br/>another screen count"| K["The current profile<br/>stays active"]
P["You pick a profile<br/>by shortcut or menu"] --> A
A hand-written config declares bindings in init.lua with
bind_profile_to_desktop. Each Desktop remembers which space it
was on, on every screen, and your main screen’s Desktops remember
which window you had focused. A window that takes more than a few
seconds to come back keeps whatever macOS focused (Accepted
limitations).
Where macOS exposes its window-management bridge (present on
macOS 26.6.1, checked 2026-08-18), KiwiDesk can also drive
Desktops: focus_desktop(n), move_to_desktop(n) and
move_to_desktop_and_follow(n) (Lua
reference). Where it is absent
the commands do nothing and log why; KiwiDesk never asks you to
turn SIP off.
Accessibility permission missing?
Window management pauses: the menu bar icon shows a warning
triangle and the quick menu’s Window Management Paused… row
reopens the permission tour. Add KiwiDesk under System Settings ›
Privacy & Security › Accessibility; management resumes on its
own.
Settings window won’t open, or KiwiDesk seems stuck?
Run kiwidesk service restart in a terminal, or quit and reopen
KiwiDesk. Launching KiwiDesk while a copy is already running
never starts a second one: the second launch brings the running
instance forward and exits with already running.
Windows come back in the wrong spaces after a restart or wake? General ▸ Advanced ▸ Discard Saved Window Arrangement clears
the remembered arrangement without changing settings. Reset
All Settings… below it is the last resort: it deletes every
profile, your spaces, layouts and keybindings and reseeds the
defaults, keeping init.lua, your palettes, the display language,
the login item and onboarding; the old files go to the Trash.
Reporting a bug? General ▸ Advanced ▸ Export Log… saves KiwiDesk’s log for a
time range to a file. Make the problem happen again first: the
range counts back from now, and “since KiwiDesk started” means
this run. The file names the apps and windows KiwiDesk managed,
so glance through it before attaching it. The terminal form is in
the CLI reference.
Shortcut not working?
Check Shortcuts for a ⚠️ marker (Conflict
Detection). If you hand-edited, run
kiwidesk reload_config.
Typo in init.lua?
A misspelled function name (scroll.set_width for
scroll.set_slot_size) does not abort the config: the call is
skipped with a did-you-mean hint and the rest still runs. Every
typo is listed under menu bar › Config Issues….
Windows aren’t tiling?
Ensure the space’s layout is not Floating and the app is not in
float_rules. On a hand-written config, make sure init.lua
exists and the app is managing tiling (check the banner).
Profile not loading after monitor change?
See Which Profile Loads: a new hardware
combination uses the built-in Standard until you Save on it.