Themes

Four themes ship with the editor. Each one decides its greys, its corner radii, its rule weight, and how a glyph mark is drawn.

StatusThemes are built in; a custom one means editing the token file and rebuilding
StabilityThe token file's shape is settling and can still change

Switching

View → Theme switches the palette. The menu tick follows the choice, and the status line names the theme you moved to.

What ships
DarkNear-black canvas, hues at full strength.
MidnightDarker still, with the panels at the canvas tone.
GrayThe default. Mid grey with dark ink, square corners, and rules in the text colour.
LightNear-white, with the hues stepped down to hold up on it.

One File

Every theme comes from themes/runebender.theme.json in runebender-core. The editors read the same file, so a change lands in all of them at once and none of them can drift.

Colour is authored in OKLCH rather than hex. Lightness and chroma mean the same thing at every hue there, so a set of colours reads as one family instead of a pile. A theme names a token, never a colour:

"surfaces": { "canvas": "neutral.15", "outline": "neutral.23" },
"roles":    { "accent": "green.dim", "danger": "red.dim" }

A token is a family and a step. neutral.15 is the grey ramp at 15% lightness. green.dim is the green hue at its dim step.

What a Theme Decides

The blocks of a theme
surfacesThe grounds: app, panel, control, button, buttonHover, field, outline, divider, canvas.
textprimary, secondary, muted, subdued, overlay, glyph.
rolesThe meanings: accent, danger, selection, the point types, curvature, kerning. Around thirty.
geometryCorner radii and rule weights.
markStep, markStyleHow a glyph mark is coloured and drawn.

Every block is optional except the colours. A theme names only what it changes, and the rest falls back to the file’s defaults.

Shape Is Themed Too

A theme sets its own corners and rule weights, because those carry as much of a look as colour does. The same palette at radius 0 with a 2px rule reads as a drawing tool. At radius 6 with a hairline it reads as a document application.

geometry
radiusThe default corner, on small chrome.
radiusControlPressable tiles: toolbar tiles, sidebar tabs, toggles.
strokeThe ordinary rule, on panels and chrome.
strokeEmphasisRings that mark a thing selected or grabbable.

The tokens are named for what they are used on, not for their size. A theme that disagrees about the sizes still knows what each one is for.

Gray names only the two radii, at 0, and lets both rule weights fall back. Naming part of the geometry is the normal case: it wanted square corners, not heavier rules.

strokeEmphasis is its own value rather than stroke doubled. Doubling works from a 1px base and breaks from a 2px one, where it puts a 4px ring on an 18px swatch.

Marks

A glyph stores what its mark means, not the colour you see. The colour comes from the theme, so the same font reads correctly in all of them.

How a mark is drawn is also the theme’s choice, and it has to be. Tinting the cell’s rule works where the ground is far from mid lightness. On a mid grey it cannot. A colour saturated enough to read as a hue sits at mid lightness too, and mid on mid is invisible.

markStyle
borderTint the cell’s rule and label. Dark, Midnight and Light.
fillFill the cell with the hue and key it with a rule. Gray, with markOutline and markInk naming the keyline and the label on top.

markStep picks which step of each hue the theme uses. Dark takes base, Gray takes base under a keyline, Light takes deep.

A theme switch never rewrites a font. The public.markColor other editors read is frozen in the token file, so it does not follow the display tuning.

The Ideas Behind It

Five decisions explain most of what the file does, and why a theme that ignores them stops looking like one thing.

Author in OKLCH, not hex. Lightness and chroma mean the same thing at every hue there. That is what makes a set of colours read as one family. The palette this replaced ran from L 0.56 to L 0.94 and had three greens nobody had compared.

Give each hue its own pair. A flat ramp holds every hue to the chroma only blue and green want. The result looks washed out. Yellow is light and red is not, so yellow sits at L 0.82 and red at 0.61. Steps are offsets from a hue’s own pair, never absolutes.

One text colour. There is no second, lighter grey for secondary text. Hierarchy comes from rules and fills, not from greying words out. The Gray theme goes further. Its rules are drawn in the text colour, so the borders draw the layout instead of tints doing it.

Shape carries as much as colour. Corners and rule weights are part of a theme, not constants in the frontend. A palette can be right and the thing still look wrong because everything is rounded.

Name a token for its use, not its size. radiusControl tells a frontend what to do with a button. radiusMedium makes it guess. The same goes for strokeEmphasis, which exists because a selection ring is a decision, not arithmetic on another token.

The hard case is the mid grey. On a near-black or near-white ground a hue can always go the other way. On a mid grey it cannot, and no amount of palette tuning fixes it. That is why the treatment is a theme’s decision and not just its colours.

Writing One

Themes are compiled into the editor. There is no folder to drop a theme into, and no setting that points at one. A theme of your own means editing the token file and building from source.

  1. Clone runebender-core and runebender-gpui side by side.
  2. Add a block under themes in themes/runebender.theme.json. Copy the theme closest to what you want and change what differs.
  3. Add the theme to the editor’s theme list and give it a menu action.
  4. Build and switch to it from View → Theme.
git clone https://github.com/eliheuer/runebender-coregit clone https://github.com/eliheuer/runebender-gpuicargo run --release -- path/to/Font.designspace

The tests in runebender-core check a new theme for the mistakes that are hard to see. A mark that does not clear its background. An emphasis ring lighter than the ordinary rule. A theme in the menu with no action behind it. Run them before trusting a theme you wrote.

cargo test

What Is Not Here

No theme editor, no live reload of a theme, no per-font or per-window theme, and no way to load one at runtime. The palette is one file compiled in, which is what keeps the editors from drifting apart.