A scrolling tiling window manager for macOS, built on the Niri column layout paradigm.
Nehir (Turkish for "river") — windows flow in columns, scrolling horizontally across your screen.
- Niri scrolling column layout — windows arranged in columns that scroll horizontally, with automatic overflow tabbing when stacked windows cannot fit at their minimum heights
- Workspace management — multiple workspaces with hotkey switching
- Window borders — configurable colored borders on the focused window
- Workspace bar — per-monitor status bar showing workspace names and app icons
- Focus follows mouse — optional hover focus
- Multi-monitor support — seamless window management across displays. For the best Niri scrolling experience, use an auto-hide Dock and arrange displays vertically in macOS System Settings to avoid parked offscreen windows bleeding onto neighboring monitors.
- Overview mode — bird's-eye view of all windows
- Command palette — fuzzy search for commands
- App rules — per-application layout overrides
- IPC — Unix socket for external control via
nehirctl - TOML configuration — split config under
~/.config/nehir/
After the first release is published, Nehir can be distributed from the guria/tap Homebrew tap:
brew tap guria/tap
brew install --cask nehirNehir requires Accessibility permissions after installation:
System Settings > Privacy & Security > Accessibility
# Package the app bundle
mise run package:release
# User-local install (no sudo)
mkdir -p "$HOME/Applications" "$HOME/.local/bin"
rm -rf "$HOME/Applications/Nehir.app"
cp -R dist/Nehir.app "$HOME/Applications/Nehir.app"
install -m 755 .build/apple/Products/Release/nehirctl "$HOME/.local/bin/nehirctl"Or use mise:
# User-local install
mise run install
# System-wide install
mise run install:system# Run
Nehir
# CLI control (requires IPC enabled)
nehirctl command focus left
nehirctl command switch-workspace 2
nehirctl --helpNehir includes runtime debugging and trace-capture commands. They are exposed consistently through IPC/CLI, the command palette, and hotkey handling, and appear in the Debugging & Tracing category:
- Debug: Dump Runtime State — copies the current runtime debug dump to the clipboard and writes it to the unified log
- Debug: Reset Runtime State — clears runtime debugging state and reboots tracking from a startup-style rescan
- Debug: Restart Clearing Runtime State — clears runtime debugging state and relaunches the app
- Debug: Toggle Trace Capture — default hotkey:
Ctrl+Option+Cmd+T- IPC/CLI accepts an optional
desiredStateargument (activeorinactive) for idempotent scripting:nehirctl command debug trace toggle active
- IPC/CLI accepts an optional
Stopping a trace capture writes a log bundle to:
${XDG_STATE_HOME:-$HOME/.local/state}/nehir/traces/
and copies the dumped file path to the clipboard.
For feedback/debug reports: toggle tracing on, reproduce the issue, toggle tracing off, then share the copied trace-bundle path or file. An optional Show Trace Capture Button workspace-bar setting provides the same toggle in the bar for advanced users and developers.
For IPC/CLI usage, see docs/IPC-CLI.md.
Nehir uses a split-file config layout under ~/.config/nehir/:
~/.config/nehir/
├── settings.toml # core app behavior
├── hotkeys.toml # physical keybindings
├── workspaces.toml # workspace definitions
├── apprules.d/ # one file per app rule
│ ├── com-google-chrome.toml
│ └── pip-floating.toml.sample # inactive sample
└── monitors.d/ # per-monitor overrides
└── studio-display.toml
All files are watched for changes — edits are applied live without restarting.
See Configuration Principles for the design rationale.
Two settings interact here:
moveMouseToFocusedWindowmoves the pointer after focus changes, but Nehir treats it primarily as a keyboard/command-navigation affordance. Pointer-originated focus changes do not warp the cursor: mouse hover/click, workspace bar clicks, tab overlay clicks, trackpad gestures, scroll animations, and floating-window clicks/drags all preserve pointer position.- Empty workspace command switching is the main intentional exception: when there is no focused window target, Nehir warps to the target monitor center so the pointer lands on the display you navigated to.
focusFollowsMouseis debounced and refreshes after scroll/swipe animations settle and after owned Nehir UI windows (Settings, App Rules, Command Palette) close, because those interactions may not generate a fresh mouse-move event.- With
focusFollowsMouseenabled, swipe gesture end updates the viewport selection but does not commit focus to the snapped column; final focus follows the pointer after the gesture/animation settles. - Tiled hover focus is disabled while a floating window is the active surface above the Niri layout, so moving toward that floating window does not accidentally focus and raise a tiled column behind it.
- A floating window that is merely visible but behind the active tiled window does not disable tiled hover focus.
- Hover focus is also blocked over visible unmanaged WindowServer windows and briefly suppressed after floating/unmanaged pointer interaction so clicking or dragging those windows does not immediately activate a tiled column behind them.
Nehir defaults are stored and shown as physical key chords.
- Option+Command — navigate, focus, and open UI
- Option+Shift+Command — move the focused window
- Control+Option+Command — larger-scope navigation such as workspace history and column indexes
- Hyper — physical Control+Option+Shift+Command, reserved for structural moves
For a lighter way to enter the base layer, see the Karabiner double-Command recipe.
The goal is a small set of predictable modifier patterns:
without Shift = go there
with Shift = move current window there
Hyper = reshape or move structure
# Build (debug)
mise run build
# Build and run
mise run dev
# Release build
mise run build:release
# Run tests (requires Xcode)
mise run test
# Clean
mise run cleanNehir is a highly opinionated fork of Hiro (formerly OmniWM), rebuilt around a single layout engine — Niri scrolling columns — with stripped-down controls and no backward-compatibility baggage.
The original project tried to accommodate a wide range of user requests; Nehir deliberately narrows the scope to do one thing well. We're deeply grateful to the original author for the foundation this builds on.
- Single layout model. Nehir is rebuilt around Niri-style scrolling columns instead of keeping multiple layout/control models.
- No legacy compatibility layer. Configuration, defaults, hotkeys, and behavior are allowed to change to fit Nehir's narrower workflow.
- Required motion stays enabled. Unlike Hiro/OmniWM's user-toggleable animation preference, Nehir treats layout motion as part of the interaction model: disabling it makes Niri-style scrolling, resizing, and transition state hard to follow, so there is no
animationsEnabledsetting. - Split TOML configuration. Runtime config is organized under
~/.config/nehir/with separate files for settings, hotkeys, workspaces, app rules, and monitor overrides. - Close/collapse focus stays local. When macOS reports another same-app window as focused after closing or collapsing the current one, Nehir treats that as native fallback focus rather than user navigation. Same-app fallback to inactive workspaces is ignored, and unmanaged quick-terminal fallback is also ignored on the current workspace so the viewport does not scroll to that app's managed column. Explicit Nehir focus commands still take precedence.
- Configurable gesture scroll snap. Trackpad swipe gestures can snap to column boundaries or stop freely mid-scroll. Controlled by
gestures.scrollSnapinsettings.toml(defaulttrue). - Smarter mouse focus and cursor warp. Pointer-initiated focus no longer makes
moveMouseToFocusedWindowjump the cursor, and hover focus is constrained around floating/unmanaged windows to fit the Niri layout model. - Built-in runtime debugging tools. Nehir now ships command-palette and IPC/CLI actions to dump runtime state, reset/rebootstrap runtime state, restart while clearing runtime state, and capture runtime trace bundles under
${XDG_STATE_HOME:-$HOME/.local/state}/nehir/traces/.
GPL-2.0-only