SlideScroll
A scrolling window manager for macOS.
Your windows live on an endless horizontal strip. Opening one never squeezes the others.
Download ·
Features ·
Setup ·
Shortcuts ·
FAQ ·
Roadmap
▶ Watch the demo on YouTube (40 seconds)
Tiling window managers split your screen into ever-smaller tiles. SlideScroll does
the opposite: every window is a column on a strip that runs past the edges of
the screen, at the width you chose. Open a window and it slides in beside the one
you're using; nothing else changes size. Swipe with four fingers or press a
shortcut to scroll the strip, the way niri and
PaperWM work on Linux.
SlideScroll is free, open source, and a native Mac app, with no Electron, no web
views and no account. Its only network access is
checking GitHub for updates, which Settings can turn off.
Features
- Scrolling strip. Columns keep their width. Scroll by trackpad swipe (1:1 with
your fingers, then it snaps to a column), by shortcut, or by clicking or
Cmd-Tabbing to a window.
- Widths that make sense. Cycle through ¼, ⅓, ½, ⅔, ¾ of the screen, go full
width and back, centre a column, or nudge it wider and narrower.
- Stacks. Put several windows in one column, one above the other. Drag a window
onto another to stack it, drag the edge between them to share the height, or
pop it back out.
- Tabs. Turn a stack into a tabbed column with a clickable tab bar. Native macOS
tabs (Finder, Terminal, Ghostty, …) stay in their column too.
- Pinning. Keep a column at the left or right edge while the rest scrolls past.
- Always know where you are. While you move, a row of app icons shows every
column, with yours highlighted; click one to jump there. The menu bar menu
lists every window too.
- Undo.
⌃⌥Z puts back the last layout change, whether a shortcut or a
drag did it, and ⌃⌥⇧Z redoes it. ⌃⌥⇥ jumps back to the window you were
just in.
- Multiple displays. Each display has its own strip. Send windows between
displays with a shortcut or by dragging, and nothing spills onto the
neighbouring screen.
- Plays well with macOS. Ignores full-screen apps and other Spaces, floats
dialogs and fixed-size windows, survives sleep, locking and displays coming and
going, and remembers your layout across restarts.
- Every shortcut is yours. Remap any action in Settings, with two shortcuts per
action. Works on any keyboard layout.
- Scriptable. Every action has a
slidescroll:// URL and a command-line name,
for Raycast, Alfred, Shortcuts and scripts. See Scripting.
- Widths per app. New windows of an app can open at their own width, say a
third for the terminal and two thirds for the browser.
- Safe. Quitting, pausing or even a crash puts every window back on screen.
- Tells you what's wrong. Settings → General checks for macOS settings that
get in the way, live, and one click opens the right System Settings pane.
Requirements
- macOS 13 Ventura or later
- A Mac with Apple silicon (M1 or later)
- A built-in trackpad or Magic Trackpad for swipes (everything else works with a
keyboard and mouse)
Install
Paste this into Terminal. It downloads the latest release, checks it against
the release's checksums, puts SlideScroll in Applications and opens it; run it
again to update. It's site/install.sh if you'd like to read it first.
curl -fsSL https://slidescroll.majnioui.me/install.sh | sh
Or install the disk image by hand:
- Download
SlideScroll-<version>.dmg from the
latest release.
- Open it and drag SlideScroll into Applications.
- Open SlideScroll from Applications.
[!NOTE]
The disk image isn't notarized yet (the Terminal install above avoids this).
macOS says it "could not verify" SlideScroll the
first time you open it. Click Done, then go to System Settings → Privacy &
Security, scroll down to "SlideScroll" was blocked and click Open Anyway.
Or run xattr -dr com.apple.quarantine /Applications/SlideScroll.app once.
Or build it from source with Homebrew (it builds with the Swift
that comes with the Xcode Command Line Tools):
brew tap majnioui/slidescroll https://github.com/majnioui/SlideScroll
brew install slidescroll
Homebrew prints how to link it into Applications. Update with
brew upgrade slidescroll; each build has a new signature, so allow
Accessibility again afterwards.
Updates
SlideScroll checks for a new release shortly after it starts and every six
hours. When there is one, it downloads it in the background, checks it against
the release's checksums, and offers Restart to Update in its menu and in
Settings → General. Restarting puts every window back on screen, swaps in the
new version and opens it. Turn the checks off, or check now, in Settings → General.
Releases are signed with the same certificate, so updates keep the
Accessibility permission. Builds signed ad hoc (your own, or Homebrew's) need it
again after each update: SlideScroll clears the old build's entry from the list by
itself, so just turn SlideScroll on again when macOS asks.
First launch
SlideScroll moves other apps' windows, which needs the Accessibility permission.
On first launch macOS asks for it: open System Settings → Privacy & Security →
Accessibility, turn on SlideScroll, and it starts right away. No restart, and
no other permissions: no Screen Recording, no Input Monitoring, no disabling SIP.
SlideScroll lives in the menu bar, as an icon of three columns side by side. Its
menu has Pause, Settings… and Quit. To start it with your Mac, turn on
Launch SlideScroll at login in Settings → General.
Recommended macOS settings
A few macOS features fight over the same gestures and windows. SlideScroll checks
them in Settings → General and lists any problem there, with a button to the
System Settings pane that fixes it. It never changes a macOS setting itself.
| Setting | Where | Set it to | Why |
|---|
| Swipe between full-screen applications | Trackpad → More Gestures | Off | SlideScroll scrolls with four fingers. macOS often keeps acting on four fingers even when this shows "three", and a fast four-finger swipe lands as three first. Switch desktops with ⌃← / ⌃→ or Mission Control |
| Drag windows to screen edges to tile | Desktop & Dock → Windows | Off | macOS snaps dropped windows into tiles |
| Drag windows to menu bar to fill screen | Desktop & Dock → Windows | Off | same |
| Hold ⌥ key while dragging windows to tile | Desktop & Dock → Windows | Off | same |
| Stage Manager | Desktop & Dock | Off | it moves and resizes windows too |
| Other window managers | Rectangle, Magnet, Loop, Moom, … | Quit them | two apps moving the same windows |
If macOS still switches desktops after you turn the swipe off, log out and back
in: trackpad changes sometimes only apply then.
Set Swipe between full-screen apps to four fingers and Swipe between pages
to two fingers, turn off Three Finger Drag (Accessibility → Pointer Control
→ Trackpad Options), and choose Three fingers in SlideScroll's
Settings… → Windows.
Using SlideScroll
The strip. Each window is a column. The focused column is always fully on
screen; columns past the edges wait just off screen, with a sliver showing. Focus
a column and the strip scrolls just far enough to show it.
Swiping. Four fingers left or right drag the strip under your fingers. Let go
and it glides to the nearest column. On multiple displays, the swipe scrolls the
display under the pointer. Settings… → Windows switches to three fingers or
reverses the direction.
Several displays. Each display has its own strip. Focusing left or right past
the end of one strip carries on to the nearest column of the display beside it.
Dragging with the mouse.
- Drag a window by its title bar and drop it between two columns to reorder.
- Drop it with the pointer over the bottom quarter of another window to stack
it below, or the top quarter (just under the title bar) to stack it
above. A highlight shows where it will land.
- Drop it on another display to move it to that display's strip.
- Resize a window's side to set its column's width. Resize the edge between
two stacked windows to share the height. A lone window keeps the height you give it.
Floating. Dialogs, panels and windows that can't be resized float above the
strip on their own. Press ⌃⌥T to float or unfloat any window.
Pausing. Pause in the menu bar brings every window on screen and leaves them
alone, with your shortcuts going to apps as usual, until you Resume.
Shortcuts
All shortcuts use ⌃ Control + ⌥ Option by default, so plain ⌥ stays free for
typing accented characters. Every one can be changed: see Customising.
| Action | Shortcut |
|---|
| Focus left / right | ⌃⌥H ⌃⌥L or ⌃⌥← ⌃⌥→ |
| Focus up / down in a stack | ⌃⌥K ⌃⌥J or ⌃⌥↑ ⌃⌥↓ |
| Focus first / last window | ⌃⌥⌘← / ⌃⌥⌘→ |
| Jump to window 1–9 | ⌃⌥1 … ⌃⌥9 |
| Jump back to the previous window | ⌃⌥⇥ |
| Focus next display | ⌃⌥N |
| Move column left / right | ⌃⌥⇧H ⌃⌥⇧L or ⌃⌥⇧← ⌃⌥⇧→ |
| Move window up / down in a stack | ⌃⌥⇧K ⌃⌥⇧J or ⌃⌥⇧↑ ⌃⌥⇧↓ |
| Move window to next display | ⌃⌥⇧N |
| Pin column to left / right edge (again to unpin) | ⌃⌥P / ⌃⌥⇧P |
| Float / unfloat window | ⌃⌥T |
| Cycle width (¼, ⅓, ½, ⅔, ¾) | ⌃⌥R |
| Full width (again to restore) | ⌃⌥F |
| Centre column | ⌃⌥C |
| Wider / narrower | ⌃⌥= / ⌃⌥- |
| Taller / shorter in a stack | ⌃⌥⇧= / ⌃⌥⇧- |
| Equal heights (a lone window gets full height back) | ⌃⌥0 |
| Stack into left / right column | ⌃⌥[ / ⌃⌥] |
| Pop out of stack to the left / right | ⌃⌥⇧[ / ⌃⌥⇧] |
| Tabbed column on / off | ⌃⌥⇧T |
| Undo / redo a layout change | ⌃⌥Z / ⌃⌥⇧Z |
| Quit (puts every window back on screen) | ⌃⌥Q |
|
Shortcuts and swipes do nothing in full-screen apps and on Spaces SlideScroll
isn't managing, so they never scroll the strip behind your back.
Customising
Settings… → Windows chooses the swipe (three or four fingers, natural or
reversed direction), the space between windows, and apps whose windows always
float: they stay where you put them, outside the strip.
Open Settings… from the menu bar and pick the Shortcuts tab. Click any
shortcut and press the new keys;
each action has two slots. Esc cancels, Delete removes the shortcut, ↺ puts an
action's defaults back. If a shortcut is already taken, it moves to the action you
just set. Shortcuts need ⌃, ⌥ or ⌘, so they can't take over typing.
Shortcuts are stored by physical key, so they keep working when you switch
keyboard layouts, and Settings labels them the way your keyboard does (on AZERTY
you'll see ⌃⌥A where QWERTY shows ⌃⌥Q).
Settings are saved to ~/Library/Application Support/slidescroll/config.toml,
which you can also edit by hand. It only lists what you've changed:
[keys]
focus_left = ["ctrl+alt+j", "ctrl+alt+left"]
cycle_width = ["ctrl+cmd+w"]
quit = [] # no shortcut
[swipe]
fingers = 3 # 3 or 4
natural = false # reverse the direction
[layout]
gap = 12.0 # points between windows, 0 to 32
show_strip = false # don't show app icons while moving along the strip
[apps]
float = ["Calculator", "System Settings"]
[apps.width] # new windows of these apps open at this width
Ghostty = "1/3" # "1/4", "1/3", "1/2", "2/3", "3/4", "full", or 0 to 1
"Google Chrome" = "2/3"
The widths are also in Settings… → Windows → Opening Width. They only apply
to new windows. Windows SlideScroll already knows keep the width they had.
Scripting
Every action has a URL, so Raycast, Alfred, Shortcuts, BetterTouchTool,
Hammerspoon and scripts can run it. The name is the action's config name,
written with dashes:
open -g slidescroll://cycle-width
open -g slidescroll://focus-window-3
open -g slidescroll://toggle-pause # also pause, resume, settings
The app's binary takes the same names, which is handy in a shell alias:
/Applications/SlideScroll.app/Contents/MacOS/slidescroll focus-left
Run it with an unknown name to list every action. Actions do nothing while
SlideScroll is paused, except resume and toggle-pause.
Troubleshooting & FAQ
macOS also uses four-finger swipes for Swipe between full-screen apps. Set it to
three fingers in System Settings → Trackpad → More Gestures (SlideScroll's menu
warns about this and opens the pane for you). If a change doesn't take effect, log
out and back in.
Open System Settings → Privacy & Security → Accessibility and make sure
SlideScroll is on. After an update or a reinstall, SlideScroll removes the entry
the previous build left there and asks again, so the switch you see is for the
build you're running. If it's on and SlideScroll still waits, remove it with
–, add it again from Applications, and relaunch.
That's macOS window tiling snapping it into place before SlideScroll puts it back.
Turn off the three tiling options in Desktop & Dock → Windows.
Check SlideScroll isn't paused (its menu bar icon is dimmed when it is), and that
another app hasn't claimed the same keys. You can change any shortcut in
Settings….
Yes. SlideScroll manages the Space you start it on and leaves full-screen apps and
other Spaces alone; switching back picks up where you left off. One strip per Space
is on the roadmap.
Quitting (from the menu, ⌃⌥Q, logging out or shutting down) brings every window
back on screen first. So does pausing, and so does a crash.
Turn on Verbose logging in Settings → General, reproduce the problem, then
click Show Log in Finder and attach slidescroll.log to a
new issue, with your macOS
version and what you expected to happen.
Privacy
SlideScroll's only network access is checking GitHub for a new release, which
Settings → General can turn off: no telemetry, no analytics, no account. It doesn't read what's in your windows or record your screen;
it only asks macOS where windows are and moves them. Your layout, settings and log
stay in your home folder.
How it works
- Layout (
Sources/SlideScrollCore): the strip as a Swift value type with no
macOS code, checked by swift run core-checks. Columns have a width; the
viewport looks at the strip from scroll. Focusing a column scrolls just far
enough to show it.
- Off-screen windows: macOS won't place a window fully off screen, so columns
outside the viewport are parked against the nearest edge with 8 pt peeking out.
On a side with another display, they park below the bottom edge instead, so they
don't spill onto the neighbour.
- Stacks and tabs: a column can hold several windows, splitting its height by
weights, or showing them as tabs under a frosted bar drawn in a non-activating
panel, so clicking a tab never steals focus.
- Displays: one strip per display. Strips follow displays being added, removed
or rearranged; a display that's unplugged and plugged back in gets its columns back.
- Moving windows: the Accessibility API (
AXUIElement). AXEnhancedUserInterface
is switched off only while a window is being moved, so apps don't animate on top
of SlideScroll and screen readers keep working.
- Events: an
AXObserver per app reports windows opening, closing, moving and
focus changes. Moves made with the mouse button held are the user's own drags.
- Animation: each window follows a critically damped spring, stepped once per
frame the display shows (
CADisplayLink, macOS 14+). Moves, resizes and focus
changes go to a queue per app that keeps only the latest request, so a slow app
(Electron, Chrome) drops its own frames without slowing the others.
- Swipes: raw finger positions from Apple's private
MultitouchSupport
framework, loaded with dlopen.
- Shortcuts: a
CGEventTap swallows key presses that match the keymap and sends
commands to the engine.
- App: the engine runs on a serial queue of its own and never touches AppKit;
the main thread has the menu bar item, the SwiftUI Settings window, the tab bars,
and sleep, lock and display notifications.
- Resilience: the engine suspends while the Mac sleeps or is locked and re-syncs
on wake, and puts every window back on screen when it quits.
- Spaces: when none of a strip's windows is on screen (another Space or a
full-screen app), that strip pauses instead of treating them as closed.
Building from source
You need macOS 13 or later and Swift 5.10 or later (Xcode, or just the Xcode
Command Line Tools).
git clone https://github.com/majnioui/SlideScroll.git
cd SlideScroll
swift run core-checks # checks for the layout model
scripts/bundle.sh # builds build/SlideScroll.app
open build/SlideScroll.app
Local builds are signed ad hoc, and macOS ties the Accessibility permission to the
signature: after rebuilding, remove SlideScroll from the Accessibility list and
allow it again. SIGN_IDENTITY="Developer ID Application: …" scripts/bundle.sh
signs with your own certificate.
To run without a bundle (the permission then goes to your terminal app):
swift build -c release
.build/release/slidescroll --list # read-only: what it sees, moves nothing
SLIDESCROLL_DEBUG=1 .build/release/slidescroll
Everything is logged to ~/Library/Logs/SlideScroll/slidescroll.log, and to the
terminal when there is one. scripts/make-icon.sh redraws the app icon from
scripts/icon.swift.
Releasing
Bump VERSION, add the release to CHANGELOG.md, commit, and check that
CI is green on main. Then push a matching tag:
git tag v0.3.4 && git push origin v0.3.4
The Release workflow runs the checks, builds the
app and publishes SlideScroll 0.3.4 with a disk image, a zip and checksums.
scripts/package.sh makes the same files locally in build/dist. Builds are
signed ad hoc unless the signing secrets listed in the workflow are set.
Signing. Without signing secrets, releases are signed ad hoc and users
allow Accessibility again after each update. scripts/make-signing-cert.sh
makes a free self-signed certificate and prints the three secrets to add;
releases signed with it keep the permission across updates. A Developer ID in
the same secrets, plus the notarization ones, also removes macOS's first-launch
warning.
Roadmap
Done:
Planned:
Contributing
Bug reports, ideas and pull requests are welcome. For anything bigger than a small
fix, please open an issue first
so we can agree on the approach. Layout logic belongs in SlideScrollCore with a
check in Sources/CoreChecks; run swift run core-checks before sending a pull
request.
License
MIT © 2026 majnioui