A Tab Bar Menu in Emacs

One of the first customizations folks make to their Emacs is to hide the menu bar. This makes some sense if you’re using a completion UI like Ivy: it’s usually faster and more convenient to find an option using fuzzy completion in the minibuffer than it is to mouse over a series of menus. Also, the menu covers only options that are relevant to the end-user. If you want fine-grained customization or access to Emacs’ API, you’ll need to use a different interface (like customize). The menu is thus seen as a crutch for new users to outgrow.

It’s a shame, though. The menu bar serves two useful purposes:

  1. Discoverability: M-x and the describe-* commands are good for “keyword searches”, i.e finding exact names for features that you know exist, including a rough idea of what it might be called ("…probably ends in -hook?"). But what about the unknown unkowns? Unless you chance upon features you want when looking for something else, you’re out of luck. The menu bar is a helpful guide to discover features. CUA-mode, one of the most common new user requests is front and center in the menu bar:

  2. Organization: The menu bar for any major-mode usually contains a comprehensive collection of options for that mode. Notwithstanding some recent improvements to command filtering, the M-x interface throws up the entire bag of Emacs’ commands at you irrespective of the context in which it’s called. The describe-* commands are worse. In contrast, the menu bar changes depending on what modes you have active, and generally provides a relevant set of actions. For example, the menu bar in programming modes provides options for linting, compilation, tracing, code folding and so on.1 The menu in LaTeX or Org-mode is quite different.

But if we’re honest, the real reason the menu bar is avoided tends to be more superficial: it takes up space and does not compose visually with Emacs’ theming.

Let’s address this too. There are at least two non-invasive approaches to using the menu in Emacs.

The text-mode menu

There’s a text-mode menu built into Emacs. Calling tmm-menubar (bound to M-` by default) will allow menu-bar access from the minibuffer (or equivalent), helping with discoverability and organization as before:

The tab-bar menu

The second method requires Emacs 28. You can shove the menu into the tab-bar:

To do this, you add the built-in function tab-bar-format-menu-bar to tab-bar-format, which is the list of widgets to show in the tab bar.

(add-to-list 'tab-bar-format #'tab-bar-format-menu-bar)

Contrary to its name, the tab-bar can show arbitrary information. For example, the system time and battery information in addition to the menu and open tabs:

I use tabs (as workspaces) and the tab-bar already, so I prefer this method for accessing the menu occasionally.2


  1. Of course, this supposes that the relevant features are available in Emacs either as built-ins or through an installed package. ↩︎

  2. I changed the menu button text to 𝞴 from the default to further shorten it. ↩︎

Add a comment, or comment via email





Duy
2022-07-31

Hi Karthik, first of all many thanks for all your great posts and packages! You are helping me a lot in learning emacs and elisp.

I have a question about your tab-bar layout above. I really like it and would like to replicate it somehow. Looking at the configs on your Github, I am able to replicate most of it. The only elements I couldn’t find in your configs and figure out myself are the following:

  • Margins in the (active) tab-button. You seem to have some space in front and behind “CONSULT-REFLEX”, whilst for me the margin seems to be zero in the button. I changed tab-bar-button-margin but it doesn’t seem to do anything.
  • You seem to have a different font for your battery indicator. How do you change the font of this but not the time?

Many thanks!

Karthik
2022-07-31

@Duy:

Glad you find the posts useful.

  • Margins in the tab-names: It looks like I’m using a custom format for tab names:
(defun tab-bar-tab-name-format-comfortable (tab i)
  (propertize (concat " " (tab-bar-tab-name-format-default tab i) " ")
              'face (funcall tab-bar-tab-face-function tab)))
              
(setq tab-bar-tab-name-format-function #'tab-bar-tab-name-format-comfortable)

It just adds spaces around the tab name.

  • The battery font looks the same as the time indicator to me. The face is sml/battery, which is set by smart-mode-line, the mode-line package I use. I haven’t manually configured it in any way. I think smart-mode-line changes the color of the display depending on the battery percentage.