A small menu bar tidier for macOS 27.
Pick the apps whose icons you don't need all the time. They disappear. Click the chevron to bring them back.
macOS 27 rebuilt the menu bar, and it broke the trick every menu bar utility relied on. Items are no longer separate windows, so nothing can enumerate them the old way, and inflating a spacer to shove icons off the edge no longer works the way it used to.
JustHide doesn't fight that. It uses the concealment facility macOS 27 has built in, so hiding is instant, nothing slides around, and the menu bar's layout is never touched.
It does one job. There is no floating bar, no icon previews, no groups, no profiles.
- macOS 27 (Golden Gate) or later
- No Screen Recording, no Developer ID, no sandbox exceptions
- Accessibility is optional, and never asked for at launch.1
brew tap benjustjammin/tap
brew trust benjustjammin/tap # Homebrew 7 will not load a cask from an untrusted tap
brew install --cask justhideReleases are signed with a Developer ID and notarised, which matters for more than Gatekeeper: macOS matches a permission grant against the app's signature, so a properly signed build keeps your Accessibility permission across updates instead of asking again every time.
Or build it — it takes a few seconds and needs only Xcode's command line tools:
git clone https://github.com/benjustjammin/justhide.git
cd justhide
./build.sh
cp -R build/JustHide.app /Applications/
open -a JustHideEither way, a chevron appears in your menu bar. Install it somewhere inside an
Applications folder: macOS only honours the concealment allowlist for an app running
from one, so a copy left in ~/Downloads or a build directory will look like it is
working and hide nothing.
Rebuilding? An ad-hoc signature changes with every build — which quietly invalidates the Accessibility grant you just gave it, so the permission appears to come undone on its own.
build.shavoids that where it can: it signs with your Developer ID Application certificate if you have one, else a self-signed certificate calledJustHide Dev, and only falls back to ad-hoc when there is neither. Make the self-signed one in Keychain Access (Certificate Assistant → Create a Certificate → Self Signed Root, Code Signing) and the script picks it up automatically.tccutil reset Accessibility dev.justhide.appclears a stale entry if one gets stuck.
- Click the chevron to show your hidden icons, click again to put them away.
- Right-click it for Settings and Quit.
- Set a keyboard shortcut in Settings if you'd rather not aim at the menu bar.
- Icons hide themselves again after a delay you choose, and never while your pointer is up in the menu bar.
In Settings, + opens a list of your apps with their icons: the ones with menu bar icons right now first, then any that have had one before and are still running. Search it, pick several at once, or use Other App… for something that isn't running. ↻ re-reads the list — and the Accessibility permission with it.
Each row in that list has two more columns. Hidden is the tick box that decides whether the app's icons are concealed; apps arrive ticked, and unticking one keeps it in the bar while leaving it in the list. Shortcut gives one of its icons a key combination that opens that icon's menu or panel without revealing it — the hidden thing answers where it would have been, and the same keys close it again. Apps that publish names for their icons (iStat Menus, Vorssaint) let you pick which one; an app with a single icon needs no name. This one needs Accessibility.
JustHide never moves anything. Where each icon sits is macOS's business (see below), so ⌘-drag them into the order you want and macOS will remember it.
Honest list of the things that will surprise you, all of them consequences of how macOS 27 works rather than choices:
- Hiding is per app, not per icon. An app with several icons hides all of them together.
- Position means nothing here. A hidden icon is not moved to one side, it simply isn't drawn, so the JustHide symbol is not a boundary with hidden things behind it. Which side of it an icon sits on is macOS's choice, remembered per app — and it is remembered in that app's preferences, which is why the order never follows the order you opened things in. ⌘-drag is the only thing that changes it, yours or anyone's: no app can move another app's icon on macOS 27.
- While icons are hidden, clicking the clock won't open Notification Center — unless you turn on Clock opens Notification Center, which stands the concealment down for as long as the panel is open and puts it back when you close it. That costs a brief flash of your hidden icons, and needs Accessibility. Left off, swipe in from the right edge or reveal first. Wi-Fi and Control Centre are unaffected either way.
- This uses part of macOS that Apple doesn't document. If an update breaks it, JustHide says so on its menu bar symbol and offers you the older layout-based method there and then — in the dialog, in the symbol's menu, and in Settings. Nothing is buried in the log.
macOS 27 has an allowlist-based concealment facility — the one behind exam "assessment mode". JustHide holds an assertion naming every running app except the ones you've hidden, and macOS conceals the rest. Dropping the assertion reveals them.
Because it's an allowlist rather than a layout trick, there's no spacer item taking up room, no icons sliding across the bar, and nothing to calibrate per display.
The allowlist is kept current rather than taken once. An app launched while icons are
hidden isn't on the list yet, and macOS would conceal it although you never asked —
so JustHide watches for new apps, waits until one has actually put an icon up, and
re-applies. Two details that took measuring: NSWorkspace's launch notification never
fires for LSUIElement apps, which is exactly what a menu bar agent is, so the watch
is KVO on runningApplications; and the new assertion goes up before the old one
comes down, which reveals as cleanly as it conceals and leaves nothing to flicker.
The private surface it touches is small and loaded defensively — every class and selector is checked before use, and it reports itself unavailable rather than crashing if a future macOS moves things:
/System/Library/PrivateFrameworks/MenuBarClientCore.framework
MBAssessmentModeConfiguration -initWithAllowedSystemItems:allowedBundleIdentifiers:
MBAssessmentModeAssertion -activateWithConfiguration:completionHandler:
-invalidate
The fallback is the older approach: inflate a divider so items overflow behind the
system's own chevron, with extra spacer items to cover a wider second display. It
works without any private API, at the cost of a gap in the bar and icons visibly
sliding on every toggle. It's kept because a private API can vanish in a point
release, and it is reached from the app: a refused or missing assertion marks the
symbol, offers the switch, and explains itself in Settings. --width runs it for one
launch without remembering the choice.
Its items — symbol, spacers, divider — are placed by CREATION ORDER, because macOS puts a status item it has never seen before at the far left whatever order it was made in. That is also the trap: how many spacers are needed depends on a length calibration that isn't known until the first collapse, so a later launch can want a spacer whose name has never existed — and that one lands left of the divider, where its width does no pushing, silently, with icons leaking. So the count is pinned per display arrangement. Needing more is recorded, applied at the next launch under a fresh generation of names (which restores creation-order placement, at the cost of one ⌘-drag), and said out loud in Settings rather than left to the log.
Settings says whether this is the latest version, next to a button to the project page. Once a day, JustHide asks GitHub for the latest release and compares the tag with its own version; if there is a newer one it says so in Settings and in the symbol's menu. Nothing is sent -- no identifier, no version, no query string -- and the switch in Settings turns it off.
It is not an updater. It offers the download, or for a Homebrew install the one command that does the job properly:
brew upgrade --cask justhideThat distinction matters more than it looks: the cask quits the running copy before replacing the bundle, and a copy that is holding a concealment assertion when its bundle is swapped underneath it leaves the menu bar in whatever state it last applied.
Diagnostics are deliberately readable, which is more than the mechanism it replaces managed:
log show --last 5m --predicate 'subsystem == "dev.justhide.app"'--simulate-unavailable and --simulate-failure pretend the concealment facility is
gone or refuses, which is the only practical way to see what an unlucky macOS update
would look like.
JustHide --list prints your displays, every menu bar item it can see, and whether
Accessibility is granted. JustHide --apps prints what the app picker would offer you
and why. Both are the first things to run if something looks wrong, and the output is
safe to paste into an issue.
Don't judge the permission from a terminal. macOS attributes a launch to the responsible process, so a JustHide started from a shell —
openincluded — inherits your terminal's Accessibility grant and will cheerfully reporttrusted: truewhile the app itself has none. Launch it from Finder and read the notice in Settings instead. This cost me an hour and a wrong answer.
There are a few other diagnostic modes left in on purpose, because they are how the
behaviour above was established rather than guessed: --tryassert <bundle-id> holds a
concealment assertion for twelve seconds, --tryclick [--cmd] checks whether a
synthetic click reaches the menu bar, and --trydrag works through every way of
synthesising a cmd-drag. On macOS 27.0 none of the drag strategies move an item, which
is why nothing here tries to rearrange your icons for you.
Things measured here that cost real time, in case they save someone else's:
- Status items aren't windows.
CGWindowListCopyWindowInforeturns nothing at the status window level; the bar is composited in the Window Server. Accessibility (AXExtrasMenuBar) is the only way to read it, and it needs the permission just to look. - You cannot move another app's icon. Every route for synthesising a cmd-drag was
tried — HID and session taps,
postToPidat ControlCenter, MenuBarAgent and SystemUIServer, faithful drags, teleports, warping the real cursor, Command as flags versus a real key press. None reorder an item, though doing it by hand still works. NSMenuItem.imageisn't drawn. Not an app icon, not an SF Symbol, not on enabled items. That is why the app picker here is a sheet with a table rather than the pop-up menu it started as — a table draws the same images without complaint.- An
autosaveNameresets an item's position. Setting one gives the item a new identity, so macOS forgets the slot it had and places it as new, at the far left. A preferred position (NSStatusItem Preferred Position <name>, larger is further left) is honoured for your own item, but only when the item is created — moving one later means removing and recreating it. - An allowlist entry is only honoured for an app in an Applications folder. The
same ad-hoc signed bundle run from
/private/tmphad its icon concealed by every assertion whether it was on the list or not; copied to/Applicationsit was left alone. Signing is not what decides it, which is probably where the belief that assessment mode needs Developer ID comes from.
Sources/
AssertionController.swift the app: chevron, conceal/reveal, auto-hide
AssessmentMode.swift the macOS 27 concealment assertion
PreferencesWindow.swift settings, built in code
AppPicker.swift the "add an app" sheet (a table, because menus can't
draw icons on 27)
AccessibilityAccess.swift asking for, and re-checking, the one permission
Settings.swift what the user can change
GlobalHotkey.swift keyboard shortcuts (Carbon, needs no permission)
MenuBarItemShortcuts.swift pressing a hidden icon from the keyboard
MenuBarApps.swift which apps own menu bar icons
AXMenuBarItems.swift reading the bar through Accessibility
LaunchAtLogin.swift SMAppService
JustHide.swift glyphs
Log.swift public os_log
WidthController.swift the --width fallback mechanism
CollapseCalibrator.swift measuring what the fallback may claim
MenuBarGeometry.swift display geometry for the fallback
SpacerPlacement.swift spacer placement for the fallback
ItemMover.swift synthetic cmd-drag (diagnostics only; see below)
MenuBarItems.swift the old window-list route, kept as evidence
tools/make-icon.swift draws the app icon
build.sh compiles and assembles the bundle
No Xcode project: one target, a handful of files, and a shell script is less to keep
in sync than a pbxproj.
./release.sh builds, signs, notarises, staples, zips and checksums a release;
--publish also creates the GitHub release and bumps the cask in
benjustjammin/homebrew-tap. Tagging
v* does the same thing on CI. The version comes from CFBundleShortVersionString,
so the tag, the zip and the cask cannot drift apart.
docs/RELEASING.md covers the one-off setup: creating a Developer ID certificate without Xcode, storing notarisation credentials, and which secrets CI needs.
The assessment-mode approach was found via
jordanbaird/Ice#995, which adapted it
from Thaw's PlatformRuntimeKit by way of
Barometer. Both are GPLv3. The
implementation here is independent, but the idea is theirs and they deserve the
credit — if you want a full-featured menu bar manager rather than this, go and use
Thaw.
Descended in spirit from Hidden Bar (MIT), which is what I wanted to keep using.
GPLv3. The code here is written from scratch, so a permissive licence would probably have been defensible — but the mechanism that makes it work was learned from Ice and Thaw, both GPLv3, and matching them removes any argument about it. If you build on this, your users get the same freedoms.
Footnotes
-
Hiding needs no permission at all. Accessibility buys four things: listing which apps have menu bar icons right now, so the picker can show them; checking whether a newly launched app actually put an icon up before the allowlist is refreshed; per-icon keyboard shortcuts; and letting the clock through. The first two degrade quietly — you add apps from the same picker or with Other App… — but the last two do nothing without it, so Settings shows a notice when one of them is on and the permission is missing. The picker's Allow… button is the ask, and its ↻ re-reads the answer. ↩