Redesigning Overlay's right-click menus: fewer rows, no icons
Right-click an empty spot on a web page in Overlay and, until recently, you got 21 rows. The top five were Back, Forward, Reload, Find and Select All, which is roughly what you wanted. Everything below that was settings for the current tab: split view, copy the URL, open in the default browser, clear cookies, DevTools, isolated session, auto refresh, always open links in the browser, notifications, mute, User Agent, a row of colors, and Close at the bottom (the DevTools item is hidden in release builds by default). Right-clicking a linked image gave you 27 rows, close to 760 points tall, about four fifths of a 13-inch MacBook Air's screen height.
Most of those settings are things you set once and never touch again, yet they showed up on every single right-click. I'd put off fixing it for a while, partly because "keep the tab settings flat" was a decision I had made myself. This time I reversed it.
Listing every menu first
Before changing anything I listed every place in Overlay that pops a context menu. There are 15. On the tab rail: a web tab, a built-in page tab, and a group card. Inside a web page it depends on what's under the cursor: empty space, a link, an image, a linked image, selected text, a text field, a selection inside a text field, and a case where we don't know (a PDF, or a page that swallowed the right-click before we saw it). Two more in popup windows, plus the pinned extension icons and the app menu shared by the tray.
I couldn't keep that straight in my head, so I built a web prototype that draws each menu at 1:1 size from the app's actual rules, puts the candidate layouts side by side, and tabulates rows and height for every scene. Eventually I made it draggable: every item could be moved up and down, into an icon row, into a submenu, or into a hidden bin, and icons could be switched off globally or per item. When I was done it exported a text outline that I implemented from.
Dragging things around myself was very different from comparing finished options. Some of it only became obvious while dragging. Copy Tab URL and Open in Default Browser, for instance, are about the page you're looking at. They belong next to Find and Select All, not buried in tab settings.
The one constraint I couldn't break
Overlay has a borderless mode (⌘B) that hides the tab rail and the address bar and leaves only the page. In that mode the page's right-click menu is the only way to reach tab features at all.
So tab features in the page menu could be folded away but never removed. In borderless mode the first item is Exit Borderless Mode, and Copy Tab URL and Open in Default Browser also appear in the text field menu. To keep a future change from quietly breaking this, there's a unit test that enumerates both hosts (a panel tab and a popup window) against nine scene flags, 1024 combinations, and asserts that none of those items go missing for panel tabs.
What it looks like now
Right-click empty space on a page and you get, top to bottom: Back, Forward, Reload; Copy Tab URL, Open in Default Browser, Find…, Select All; Reload Group and Ungroup if the tab is in a group; any extension items; a single row of icons; More ▸; Close.
The icon row is split view, a divider, then Show Address Bar, Isolated Session, Auto Refresh When Shown, Always Open Links in Browser, Disable Notifications and Mute Tab (plus the proxy toggle if you've configured one). More holds User Agent, Clear Cookies & Cache, DevTools and the colors.
For the same scene (the tab is in a group, and a translation extension adds an item), 21 rows became 13. The linked image went from 27 to 20. The tab rail's own menu only went from 16 to 14, because I also added Back and Forward there; they act on the tab you right-clicked, which isn't necessarily the one you're looking at. Links also got a new Open Link in Split View, which opens the page in a new pane to the right of the current one.
Why the icons went away
Every item used to have an icon, carried over one-to-one from the old Tauri version. Looking at them honestly, most weren't saying much. Clear Cookies was a paintbrush. Isolated Session was a person with a question mark. You read the text to understand the icon, not the other way round. Icons also cost space: if any item in an NSMenu has an image, AppKit reserves an image column and every title shifts right.
After I removed our own icons, the screenshot still showed icons on Cut, Copy, Paste and Select All, and nowhere else. That looked worse than before. It turns out macOS 26 adds them by itself: if a menu item's action is a standard selector like copy:, cut:, paste: or selectAll:, AppKit attaches an SF Symbol, whatever the title says. I checked with a tiny test program: an item titled "Whatever" with the copy: action still got the copy icon. Undo and Redo had no icon because they don't use the standard undo: selector. With that selector AppKit rewrites the title to its own "Undo", which follows the system language, so a Chinese UI would suddenly show an English word.
The official switch is NSMenuItem.preferredImageVisibility, but it's macOS 27 only. On 26 what works is the NSMenuEnableActionImages defaults key set to NO. It has to be set before any menu is built, so it lives in main.swift ahead of NSApplication.shared, and registering it with register(defaults:) is enough, nothing gets written to the user's preferences. For what it's worth, the macOS 27 SDK's documentation for NSMenuItem.image says AppKit "will typically hide images". Apple seems to be heading the same way.
The result is no icons in any of Overlay's menus: context menus, the tray menu, the pinned extension menu, and the Edit menu in the menu bar. There's one switch for all of it if I ever change my mind.
Extension items are the exception and keep their own icons. In a menu, the icon is how you tell which extension an item came from, and stripping it would make them harder to find. The side effect is that once an extension item is present, AppKit reserves the image column for the whole menu again, and I can't control that per item.
So why an icon row?
Removing icons and then adding a row of them sounds contradictory. The difference is that in this row the icon is the control. All six toggles are states that stay on once you turn them on. Folded into a submenu, you can't see which ones are on. In a row, the ones that are on are drawn in the accent color, and you can see the state at a glance. Names appear when you hover.
Putting an icon row inside NSMenu
The first version was hand-drawn: an NSMenuItem.view, the same approach as the color row. Then I noticed the arrow keys skipped right over it. A menu item that only has a custom view has no action, AppKit treats it as disabled, and keyboard navigation goes around it.
I could have given it an action so the keyboard would stop there, then handled left and right, Return, focus drawing and VoiceOver myself, and it would still never look quite like a system menu. Instead I switched to the palette menus added in macOS 14: set a submenu's presentationStyle to .palette and its items are laid out horizontally inside the parent menu's row. The tag colors in Finder's context menu are this.
I wanted to check its keyboard behavior before committing. Synthetic mouse events don't put a menu into real tracking; that fooled me once before and cost me two fixes that didn't work. What does work is posting key events into your own process with NSApp.postEvent from a timer added to the eventTracking run loop mode, which drives the real menu tracking. With that: ↓ lands on the row, ←/→ moves between cells, Return triggers the cell. The keyboard-focused cell gets the system's blue square, items that are on get the accent color, and VoiceOver comes for free.
Three things surprised me after that.
Icon size. Give the palette an SF Symbol and it scales it up on its own terms: a 14-point symbol came out around 26×20, visibly bigger than the menu text. A plain image, on the other hand, is aspect-fit into an 18×18 box. So I draw the 14-point symbol centered on an 18×18 transparent canvas and hand it that. The scale works out to exactly 1.
The divider. An NSMenuItem.separator() inside a palette is a 32-point gap with no line. A disabled cell whose image is a vertical line does show up, and the keyboard skips it, but every cell is a fixed 31.75 points wide. I tried a 4-point image, a very wide image, an image with an alignmentRect, a 9-point custom view and the system's vertical line symbol. Same width every time, with a lot of empty space on both sides of the line. What I ended up doing is a bit rough: after the menu opens, a timer in the tracking loop looks for the cell's view every 5 ms, and when it finds it, adds a 1-point line view to that row at the boundary between the two cells. It appears twenty-odd milliseconds after the menu opens. The color is separatorColor; I measured the pixels at a brightness of 0.685 against 0.682 for the menu's own horizontal separators. It only uses public API, and if it can't find the view it simply doesn't draw the line.
Hover names. System tooltips often don't appear at all in a non-activating panel like Overlay's, and never appear for keyboard movement. My own tooltip waits 150 ms using Task.sleep, but while a menu is open the main queue doesn't run, so that delay never finishes. Now the palette's willHighlight callback, which fires for both mouse and keyboard, finds the NSImageView showing that cell's image, converts its frame to screen coordinates and draws the tooltip above it synchronously. The tooltip window sits one level above the menu, otherwise the menu covers it.
Limitations
Overlay uses the system NSMenu, so the AutoFill and Services items at the bottom of a text field's menu are added by the system at draw time. They aren't in menu.items, and I can't remove them. Apple's own TextEdit has them too. The only way to get rid of them is to draw the whole menu yourself, which buys full control over the look and costs keyboard navigation, accessibility and dark mode support. For Overlay that's a bad trade. I also haven't confirmed yet whether the Services item still shows a system icon now that the others are gone.
The divider is drawn into a system view. If a future macOS changes how this row is built, the worst case is that the line disappears; clicking and keyboard use aren't affected. The line may also be missing for the first frame or two after the menu opens.
None of this has run on macOS 27 yet. And the icon row takes a little learning. The tooltips help, but someone using it for the first time will still hover to find out what each icon does.