svg-line: Better Status Bars for Emacs Emacs developer Chiply released svg-line, a new package that renders all four Emacs status bars (mode-line, header-line, tab-bar, and tab-line) as SVG images to provide consistent multi-line layout, alignment, icons, and interactivity across them. The package overcomes native limitations that previously restricted features like multi-line support to only the tab-bar and icon display to only the mode-line and header-line. Users configure a single :content function and call svg-line-activate to enable the unified status bar system. svg-line: Better Status Bars for Emacs Table of Contents 1. TLDR Emacs provides four useful status bars mode-line , header-line , tab-bar , and tab-line , but each imposes different, inconsistent limits on multi-line layout, alignment, icons, and interactivity. svg-line see code on GitHub https://github.com/chiply/svg-line solves this by rendering them as SVG images, and normalizes a rich feature set across all status bars with a consistent configuration. svg-line works by defining a small rendering engine built on Emacs's native SVG support. Configuring status bars is easy: you simply write one :content function and call svg-line-activate . You can see my custom configuration of mode-line https://github.com/chiply/.zetta.d/blob/main/modules/ui/modeline-svg.el , , https://github.com/chiply/.zetta.d/blob/main/modules/ui/header-line-svg.el header-line , and https://github.com/chiply/.zetta.d/blob/main/modules/ui/tab-line-svg.el tab-line in my https://github.com/chiply/.zetta.d/blob/main/modules/ui/tab-bar-svg.el tab-bar Emacs config https://github.com/chiply/.zetta.d . Figure 1: Every -line in this frame is one SVG image drawn by svg-line . 2. About Emacs gives us four status bars, the mode-line , the header-line , the tab-bar , and the tab-line -lines for short . These are useful for providing a dynamic 'heads-up display', for important context, like what buffer you're in, the active major mode, and really any arbitrary thing you can define. I'm a heavy user of the -lines in Emacs, and I have them all enabled, but the issue that has plagued me is that, natively, each one behaves differently and each has unique limitations. For example, multi-line status necessary on my small laptop is possible, but only in the tab-bar . Right alignment is possible in the tab-bar , but only in the last line, and this alignment feature is only available in the tab-bar . I can display icons from all-the-icons in the mode-line and header-line , but not the tab-bar or tab-line . Etc…. What I really want is consistent behaviour and configuration across all these status bars, and I want the multi-line, alignment, and icons features available in all of them. It turns out that SVG scaled vector graphics https://en.wikipedia.org/wiki/SVG is the key to solving this. Inspired by Nicolas Rougier's dual-header gist https://gist.github.com/rougier/8d5a712aa43e3cc69e7b0e325c84eab4 , I built svg-line , which provides this experience by utilizing Emacs's built-in SVG rendering support. At first, this approach seemed like a hack, or abuse of the -lines, or neglect of the built-in status bar behaviour. But I kept it and created a package because I was literally shocked how well this works and how native this feels see the screenshot and video above . Note that even if you only use the mode-line , svg-line is still useful — likely more so, since a single status bar has to render all your indicators on its own. 3. svg-line 's Features Multi-line everywhere , with per-row left/center/right alignment. A overflowing tabs onto new rows instead of hiding them, including with file-type glyphs, a current-tab highlight, and an unsaved tint. tab-line that wraps Clickable anything. Any segment can carry a left-click action, a right-click menu, and hover help with a highlight. This works uniformly across all four bars, including the otherwise-uncooperative tab-bar . Icons as text. Using Nerd Fonts https://www.nerdfonts.com/ home and an icon is just a character that flows with everything else. SVG rendering also enables a full-height "masthead" glyph option on status bars that can span multiple lines. Dynamic and animated indicators : a buffer-position pie, progress bars, active vs. inactive styling per window. It respects text scale. The bars track text-scale , re-rendering crisply instead of blurring. A meta feature is that the configuration surface is uniform across all status bars, which is a pleasant improvement over the diverse configuration strategies for the native APIs. 4. Why SVG Works When using svg-line , each line becomes one SVG image , and SVG images are more featureful than the native text engine: It can be any height. Multi-row bars are now possible in every -line. Everything is placed at exact pixel coordinates. Left, right, and center alignment work identically on every row. It draws whatever you want. Text, yes, but also wrapped tab flows, geometric progress bars and pies, and with a Nerd Font icon glyphs inline with the text, the same on all four lines. Anything you can render in an SVG just about anything is fair game. The engine remembers where it drew. It can detect the mouse against those placements, so clicks, right-click menus, and hover all work on any element of any line. 5. Configuration Configuring svg-line is deliberately simple. You write a :content function that returns rows, supply it to svg-line-define , and call svg-line-activate on the defined line. This configuration pattern is identical for all four bars. The engine has two layouts: lines the default — rows of segments, used for the mode-line , header-line , and tab-bar and wrap a flow that wraps, used for the tab-line . 5.1. Mode-line 5.1.1. Simple mode-line The smallest useful line is a single row: a label on the left, the cursor position on the right. svg-line-define 'my-mode-line :target 'mode-line :content lambda ;; one row: LEFT-SEGMENTS . RIGHT-SEGMENTS list cons list buffer-name list format-mode-line "%l:%c" svg-line-activate 'my-mode-line This trivial example clarifies the pattern: define then activate : :content is the only required key: a function returning a list of rows . Each row is a LEFT . RIGHT cons, and each side is a list of segments — here just plain strings.- with no :background , :foreground , or :active , the line picks sensible defaults and is always drawn as active. svg-line-activate enables it, and svg-line-deactivate / svg-line-toggle disable it, restoring the native mode-line untouched. 5.1.2. Rich mode-line Here's a more complicated mode-line configuration that demonstrates svg-line 's feature scope. It defines two rows, three-way alignment, a masthead icon, a custom segment, a clickable button, dynamic theme colours, and active/inactive styling: ;; A custom segment is just a zero-argument function returning a string. ;; This one shows how far point sits through the buffer, as a percentage. defun my/buffer-percent format " %d%%" / 100 point max 1 point-max svg-line-define 'my-mode-line :target 'mode-line :active 'mode-line-window-selected-p :icon lambda nerd-icons-icon-for-mode major-mode :background lambda face-background 'mode-line nil t :foreground lambda face-foreground 'default nil t :content lambda list ;; row 1 — three independently-aligned segments on one row list :left list buffer-name :center list symbol-name major-mode :right list format-time-string "%H:%M" ;; row 2 — custom segment + position on the left, a button on the right cons list 'my/buffer-percent format-mode-line " %l:%c" list svg-line-seg "save" :id 'ml-save :help "buffer actions" :action 'save-buffer :action-help "save" :menu ' "Revert" . revert-buffer "Kill" . kill-current-buffer svg-line-activate 'my-mode-line Line by line: my/buffer-percent — any zero-argument function can be a segment; this one returns a string. :active 'mode-line-window-selected-p — a predicate; when it's false an unfocused window the engine applies the :inactive- colours instead. :icon — a full-height "masthead" glyph drawn once on the left edge, spanning both rows. This is a function, so it tracks the current buffer's mode. :background / :foreground — literal colours, or as here zero-argument functions evaluated on every render, so the bar follows your theme automatically. row 1 — a :left/:center/:right plist puts three independently-aligned segments on a single row. row 2 — a plain LEFT . RIGHT cons. Its left side mixes the custom function with an ordinary %l:%c string. svg-line-seg — turns a segment into a button: left-click runs :action , right-click opens the :menu , and :help shows on hover in the echo area. 5.2. Tab-line The tab-line is where the wrap layout is most useful: instead of scrolling overflow off the edge, it flows tabs onto subsequent rows. svg-line-define 'my-tab-line :target 'tab-line :layout 'wrap :content lambda ;; each item is LABEL . STATE mapcar lambda buf cons buffer-name buf list :current eq buf current-buffer :modified buffer-modified-p buf tab-line-tabs-window-buffers :current-background lambda face-background 'highlight nil t :modified-foreground " ebcb8b" svg-line-activate 'my-tab-line :layout 'wrap — switches from rows of segments to a wrapping flow; overflowing tabs land on a new row rather than scrolling out of sight.- each item is LABEL . STATE , where :current and :modified in the state plist drive the per-tab highlight and unsaved tint. :current-background / :modified-foreground — the same value-or-function styling as the lines layout, just with current- and modified-tab variants. 6. Acknowledgement Credit where it's due: this started as an experiment off Nicolas Rougier https://github.com/rougier 's work. His SVG explorations and that dual-header gist demonstrated that this was possible, and showed me how well this approach works.