Skip to content
codingncaffeine edited this page Sep 19, 2026 · 18 revisions

Themes

Themes are data, not code. Every color, size, type size and timing of the window's chrome comes from one JSON file, themes/default.json. Edit it while the window is open and the chrome follows.

Where the theme comes from

  • themes/default.json beside the executable, else in its parent directory
  • --theme <file.json> on the command line
  • The theme last chosen from the command palette, kept as settings.json in the profile folder ({"theme": "<file>"}, see Running Sashfold) and read before the window opens
  • Every token has a built-in default; a missing or malformed token falls back to it, and each problem is named

The presets

Four more themes ship beside the default, each a plain theme file that overrides only the colors it changes: light.json (Sashfold Light), nord.json (Nord), solarized-dark.json (Solarized Dark) and high-contrast.json (High Contrast, with a two-pixel border). Every .json in the folder beside the shipped theme that parses clean and names itself is offered by the command palette (Ctrl+Shift+P, then Theme:) and by the main menu's Themes, where the one in use is checked — drop your own file in, and it is on the list under the name it gives itself. Choosing one puts it on at once, the window follows that file from then on (edit it and the chrome follows), and the choice is kept for the next start. A theme is one file: post it anywhere, and anyone can drop it beside theirs.

The tokens

Colors — chrome-background, chrome-text, chrome-text-muted, chrome-border, tab-active-background, tab-inactive-background, tab-hover-background, address-background, address-text, address-border, accent, selection, find-highlight, find-current, hint-background, hint-text, button-hover-background, button-disabled-text, status-background, status-text, content-background, secure-indicator, insecure-indicator

Colors of a surface's own — whatever pops up over the window, a menu or the command palette, draws from popup-background, popup-border, popup-text, popup-text-muted (a shortcut, a row that is not highlighted), popup-disabled-text, popup-highlight and popup-highlight-text. A theme need not name them: one it leaves out takes that theme's own value of another token — chrome-background, address-border, chrome-text, chrome-text-muted, button-disabled-text, button-hover-background and chrome-text, in that order — never the built-in default, so a light theme written before menus existed has light menus. Every surface added from here on gets tokens of its own this way, each the counterpart of a key that Firefox and Chrome themes carry (popup, popup_border, popup_text, popup_highlight, popup_highlight_text for these), so that such a theme's colors have somewhere to land one to one.

The same goes for what the older tokens used to cover between them. The toolbar has toolbar-background, apart from the tab in front (from tab-active-background), and its buttons' glyphs toolbar-icon (from chrome-text) — the toolbar and icons of a browser theme. The tab in front has tab-text for its title (from chrome-text; the other tabs' stays chrome-text-muted) and tab-line, a line along its top edge, which is transparent unless a theme names it and sits under a container's stripe. A field with the focus — the address bar, the find box, the palette's — has address-background-focus, address-text-focus and address-border-focus (from address-background, address-text and accent), and the band over its selected text is address-selection (from selection): toolbar_field_focus, toolbar_field_text_focus, toolbar_field_border_focus and toolbar_field_highlight. None of the shipped themes names any of these, and each paints exactly as it did.

A window that is not the one in front may wear another frame: chrome-background-inactive (from chrome-background) is what shows behind the tabs then — a browser theme's frame_inactive. The system says when the window is in front (the activated state of an xdg_toplevel on Wayland, WM_ACTIVATE on Windows). No shipped theme names the token, so their windows look the same either way.

Everything over the frame composites. Give toolbar-background, tab-active-background, tab-inactive-background, tab-hover-background or address-background an alpha (#rrggbbaa) and the frame — its color, and its pictures below — shows through. A background tab whose color is the frame's, or is fully transparent, shows the frame; the pointer's color, tab-hover-background, goes over a background tab's color and pictures both, so a translucent white lightens whatever the tab wears. A box with a border — a field, a menu, the palette — draws the border around its inside and never under it, so a translucent inside shows what is behind the box, not the border's color.

Metrics — tab-strip-height, tab-height, tab-max-width, tab-min-width, tab-corner-radius, tab-gap, toolbar-height, address-height, address-corner-radius, button-size, button-corner-radius, padding, border-width, status-height, find-height, devtools-height, scroll-step, tab-icon-size

Type — font-size, tab-font-size, status-font-size

Timings — tab-hover, tab-switch

New tab ("new-tab": { … }) — backgrounds, a folder of pictures (PNG, JPEG, GIF or BMP) written relative to the theme file, which the new-tab page rotates through in name order; rotate, how long each picture stays before the next fades in, in milliseconds (0 never rotates; 20000 by default). A theme that names no folder gets a gradient of its own colors. The page's colors are the chrome's unless the theme names them here: background and text — a browser theme's ntp_background and ntp_text — with text-muted for the date and background-end for the gradient's far end. Unnamed, the page runs from chrome-background towards the accent in chrome-text and chrome-text-muted, as it always has; a background named alone is that color flat, as those browsers show theirs, and naming both ends gives the gradient between them. A new-tab page that is open when another theme is put on is made again in that theme's colors.

Pictures ("images": { … }) — frame, toolbar and tab-background: pictures laid over the chrome (PNG, JPEG, GIF, BMP or SVG), each written relative to the theme file. A surface takes one picture or a list of them, front to back, and a picture is its path alone or an object:

"images": {
  "frame": [
    "header.png",
    { "picture": "weave.png", "align": "left top", "tile": "repeat" }
  ],
  "toolbar": { "picture": "brushed.png", "align": "left top", "tile": "repeat-x" }
}

align is one or two of left, center, right, top and bottom, a side named alone leaving the other axis centered; tile is no-repeat, repeat, repeat-x or repeat-y. Unsaid, a picture is held at the top right and shown once, as a Firefox theme's is. All three surfaces are placed against one area — the tab strip and the toolbar together, from the window's top left — so that a picture runs on unbroken from the tab in front into the toolbar; they differ in where they show. frame shows behind everything in that area, over the frame's color: a browser theme's theme_frame, and behind it a Firefox theme's additional_backgrounds with their alignment and tiling. toolbar shows over the toolbar's color and the front tab's (theme_toolbar), and tab-background over every other tab (theme_tab_background). A picture's size is in CSS px, so at two device px per CSS px it is drawn twice the size, like the chrome around it. A picture that cannot be read, or is in no format decoded here, is left out and named on stderr, and the rest of the theme is put on. A surface takes sixteen pictures at most, and a picture 16 MB as a file and sixteen million pixels.

Colors are written as #rrggbb or #rrggbbaa.

How it is held honest

A unit test asserts that the shipped themes/default.json equals the built-in defaults token for token, so the file and the code cannot drift apart. The chrome goldens in CI (see Testing) are rendered from the same file. tests/shell/frame.script puts on a theme of translucent colors over held and tiled pictures and asserts pixels worked out by hand — the toolbar over the frame, a field over the toolbar, a picture's corners at one and at two device px per CSS px, the frame of a window not in front — and each rule it checks has been seen to fail with its fault planted.

Clone this wiki locally