How to Use SF Symbols in Emacs (for Tab Numbers)

Early this year, I shared my setup to get tab numbers in the form of Unicode characters like “”.

This week, I reduced the font size in the tab bar to 80% of the main font and make the tab bar stand out less in general. The numbers in circled didn’t look so great then anymore, and the baseline looked off.

I know that SF Symbols has a couple of numbers in various boxes, so I wanted to try these. SF Symbol’s number in a circle render much sharper than the old unicode numbers, but I actually prefer the numbers in boxes.

For some numbers, like "4", there are even alternative styles where the upper part of the number is open

Glyphs for special characters like emoji are taken from fallback fonts, like the "AppleEmoji" font, or "ZapfDingbats" for other special characters. It’s the same for SF Symbols: after some digging in how the macOS text system treats SF Symbol code points, I found that they are displayed using the “SFCompact” font family.

And sure enough, that also works in Emacs: Change the text’s ‘face’ to "SF Compact" and then you can copy and paste SF Symbols, and they’ll actually render.

(defface ct/tab-bar-numbers
  '((t
     :inherit tab-bar
     :family "SF Compact"
     :weight light))  ;; Looks better at smaller sizes IMHO than regular
  "Face for tab numbers in both active and inactive tabs.")

In my case I don’t want San Francisco Compact for the whole tab bar; that’s too, well, compact for my taste. So the following addresses how I limit this to the actual number, demonstrating the use of propertize to set the face of a substring along the way.

Update 2022-12-31: I figured out that SF Pro Display also works. Every “SF Pro” font might work – but SF UI Display doesn’t, which was my variable pitch font of choice until now. SF Mono doesn’t work, either. So you can’t simply change your fonts to the SF family and paste symbols in your text just yet. (A minor mode would help that applies an SF Pro font to characters in the SF Symbols range of Unicode characters.)

In my original post, I defined ct/tab-bar-tab-name-format-default and used it as the “tab name format function” that produces a (styled) string for each tab. It’s set via tab-bar-tab-name-format-function.

Before we get there, here’s the new set of numbers:

(defvar ct/box-numbers-alist
  '((1 . "􀃊")
    (2 . "􀃌")
    (3 . "􀃎")
    (4 . "􀘙")
    (5 . "􀃒")
    (6 . "􀑵")
    (7 . "􀃖")
    (8 . "􀃘")
    (9 . "􀑷")
    (0 . "􀃈"))
  "Alist of integers to strings of SF Symbols with numbers in boxes.")

These likely don’t render outside of macOS at all – but don’t fret, they also don’t render in my monospace font in Emacs, either :)

You have to trust me that these unicode graphemes correspond to the “number in a box” SF Symbol. Here’s a screenshot of a manually propertized string for these numbers in fundamental-mode (where, unlike programming modes, the properties remain):

With that in place, here’s the function to render tab names:

(defun ct/tab-bar-tab-name-format-default (tab i)
  (let ((current-p (eq (car tab) 'current-tab)))
    (concat
     ;; First, add the tab number with a custom face
     (propertize
      (when (and tab-bar-tab-hints (< i 10)) (alist-get i ct/box-numbers-alist))
      'face 'ct/tab-bar-numbers)
     ;; Add a space (unstyled)
     " "
     ;; Add tab name with the face returned by tab-bar-tab-face-function
     (propertize
      (concat (alist-get 'name tab)
	          (or (and tab-bar-close-button-show
			           (not (eq tab-bar-close-button-show
				                (if current-p 'non-selected 'selected)))
			           tab-bar-close-button)
		          ""))
      'face (funcall tab-bar-tab-face-function tab))
     ;; Add trailing space (unstyled)
     " ")))

The result, with the actual tab font applied only to the tab’s actual name, but neither to the spaces nor to the leading number, I can change the default tab-bar-tab and tab-bar-tab-inactive faces to e.g. use underline decorations without looking like crap.

I’m okay with the result:

The tab’s don’t jump out at me but fade into the background a bit more. That’s quite nice for a change!

One thing that somewhat annoys me is that at 80% height and variable width, the battery indicator and clock in the top-right corner don’t right-align properly. Then again, since macOS Monterey, I can’t remove the system clock from the main menu anymore, so I might as well remove the Emacs clock completely.

For completion, here’s the function I use to adjust the tab colors within the modus-themes colors:

(defun ct/modus-themes-tab-bar-colors ()
  "Override `modus-themes-tab-*' to have even less variety"
  (let* ((bg-color (modus-themes-color 'bg-main))
         ;; Additional padding between tabs
         (box `(:line-width
                (2 . -1)  ;; -1 for no vertical space
                :color ,bg-color :style flat-button))
         (active-accent-color (modus-themes-color 'blue-active)))
    (set-face-attribute 'tab-bar nil
                        :height 0.8)
    (set-face-attribute 'modus-themes-tab-backdrop nil
                        :background bg-color
                        :box nil)
    (set-face-attribute 'modus-themes-tab-inactive nil
                        :background bg-color
                        :box box)
    (set-face-attribute 'modus-themes-tab-active nil
                        :background bg-color
                        :underline `(:color ,active-accent-color :style line)
                        :box box)))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-tab-bar-colors)

Update 2022-12-31: Álvaro Ramírez
created an SF Symbol picker
that’s super nice! Its job is to insert the symbol names for images in e.g. SwiftUI code. But this could also be adapted to work like Emoji pickers, inserting the symbol, not its name; that would require your font to be an SF Pro variant, though.