Skip to content

Building a theme

A theme recolors Silo by overriding its design tokens. Where styling an extension is the consumer side — you read the 17 extension-safe --silo-color-* tokens — authoring a theme is the producer side: you set the full token surface, every component included. This page lists all of it.

There are two ways to ship a theme, same vars shape behind both:

  • A custom theme — authored in the app's theme editor, saved to disk as JSON, shareable by export/import. Best for personal themes and one-offs. Modelled by CustomTheme.
  • A preset — contributed by an extension through ctx.registerThemePreset so it shows up in the picker for everyone. Best for distributing a theme. Modelled by ThemePreset. The bundled Tokyo Night, Solarized Light, and Gruvbox Dark are exactly this.

The shape

Both are the same four fields plus the variable map:

ts
{
  id: "acme.midnight",       // preset id (custom themes get one assigned)
  name: "Midnight",          // shown in the picker
  base: "dark",              // "dark" | "light" — Monaco/xterm base + data-theme
  colorScheme: "dark",       // CSS color-scheme for native controls
  vars: {
    // a Partial<ThemeVars> — any subset; the base palette fills the rest
    "--silo-color-bg": "#0b0f1a",
    "--silo-color-accent": "#5eb1ff",
  },
}

vars is a Partial<ThemeVars>. You only set what you want to change — every key you omit keeps its value from the base palette in theme.css for the chosen base.

Start with the generics

Most of the surface derives from the generic colors, so a credible theme can be just the General group. Component tokens like --silo-menu-bg, --silo-modal-bg, and --silo-statusbar-bg-hover default off the generics via var(), so they re-resolve to your palette automatically until you override them:

ts
ctx.registerThemePreset({
  id: "acme.midnight",
  name: "Midnight",
  base: "dark",
  colorScheme: "dark",
  vars: {
    "--silo-color-bg": "#0b0f1a",
    "--silo-color-bg-hover": "#141a2b",
    "--silo-color-bg-active": "#1b2238",
    "--silo-color-text": "#9aa4c0",
    "--silo-color-text-hi": "#cdd6f4",
    "--silo-color-text-lo": "#5a6285",
    "--silo-color-accent": "#5eb1ff",
    "--silo-color-border-strong": "#2b3148",
    // menus, modals, the status bar, etc. follow these automatically
  },
});

Reach for the component groups below only when you want a surface to diverge from the generic palette — a darker status bar than the panels, a distinct editor background, tab colors that don't track the body text.

Button colors aren't theme keys

--silo-button-* (and its -primary-/-danger- variants) are not in ThemeVars. They derive from --silo-color-accent, --silo-color-err, and the neutral button generics, so retint buttons by moving those, not by setting a button token.

The complete vars surface

Every overridable key, grouped exactly as the in-app theme editor groups them. Every key is a color string unless noted. All are optional.

General

The generic palette. Set these first — most of the rest derives from them. This is the same set extensions are allowed to consume.

TokenRole
--silo-color-bgApp / panel background
--silo-color-bg-hoverHover surface (rows, menu items, cards)
--silo-color-bg-activeActive / pressed surface
--silo-color-button-bgNeutral control surface
--silo-color-button-textNeutral control text
--silo-color-text-hiHigh-emphasis text (headings, active)
--silo-color-textBody text
--silo-color-text-loLow-emphasis text (hints, disabled)
--silo-color-input-bgForm-field background
--silo-color-input-textForm-field text
--silo-color-borderDefault border (often transparent)
--silo-color-border-strongVisible divider / outline
--silo-color-accentAccent (links, focus, selection)
--silo-color-accent-2Secondary accent

Status

TokenRole
--silo-color-okSuccess
--silo-color-warnWarning
--silo-color-errError / destructive

Status Bar

TokenRole
--silo-statusbar-bgStatus bar background
--silo-statusbar-textStatus bar text
--silo-statusbar-bg-hoverStatus-item hover (auto-derived from the bar if unset)

Side Tabs

TokenRole
--silo-tab-textSide-tab label
--silo-tab-text-activeActive side-tab label
--silo-tab-bg-hoverSide-tab hover surface
--silo-tab-border-activeActive side-tab indicator

The single treatment behind every floating menu — context menus and dropdowns.

TokenRole
--silo-menu-bgMenu / dropdown surface
--silo-menu-textMenu item text
--silo-menu-item-hover-bgHighlighted menu item
--silo-menu-borderMenu border / row separator

Modals

The dialog shell behind ctx.ui.confirm/prompt and the SDK <Modal>. The backdrop scrim, shadow, and radius stay fixed — only the card is themeable.

TokenRole
--silo-modal-bgDialog card surface
--silo-modal-borderDialog card border
TokenRole
--silo-breadcrumb-bgBreadcrumb bar background
--silo-breadcrumb-textBreadcrumb segment text
--silo-breadcrumb-text-activeActive / leaf segment
--silo-breadcrumb-iconChevrons / icons

Content

The editor and terminal surfaces.

TokenRole
--silo-content-terminal-bgTerminal background
--silo-content-editor-bgEditor background
--silo-content-editor-selectionSelection (editor + terminal)
--silo-content-editor-selection-inactiveSelection when the view is unfocused
--silo-content-textEditor / terminal foreground
--silo-content-editor-text-dimDimmed editor text
--silo-content-editor-text-faintFaint editor text (whitespace, guides)

Content Tabs

The editor tab strip.

TokenRole
--silo-content-tab-tray-bgTab tray background
--silo-content-tab-bgTab background
--silo-content-tab-tray-textTray text
--silo-content-tab-text-inactiveInactive tab label
--silo-content-tab-textTab label
--silo-content-tab-text-activeActive tab label

Fonts optional

Two optional font-family overrides (omit to inherit the system stacks). Font sizes are driven by the user's uiFontSize and are not theme-overridable.

TokenRole
--silo-font-uiUI font stack (sans)
--silo-font-monoMonospace stack (code, terminals)

Authoring in the app

The theme editor (open it from the theme status item) is the fastest path for a custom theme:

  1. Create a theme, pick its base (dark/light), and name it.
  2. Edit by group — the editor lays the tokens out in the same groups as the tables above, each with a swatch + color picker. Changes apply live.
  3. Export JSON writes a ThemeExport (the theme minus its id) — the shareable artifact.
  4. Import JSON validates a file back into a custom theme with a fresh id and activates it.

Custom themes are persisted to disk and surfaced through ctx.theme (saveCustom, exportTheme, importTheme, …) — the editor is just a UI over that service, so an extension can build its own.

Shipping a theme as a preset

To distribute a theme so it appears in everyone's picker, register it from an extension. This is the same path the bundled presets use:

ts
const preset: ThemePreset = {
  id: "acme.midnight",
  name: "Midnight",
  base: "dark",
  colorScheme: "dark",
  vars: {
    /* the vars from above */
  },
};

export default {
  activate(ctx) {
    ctx.registerThemePreset(preset);
  },
} satisfies Extension;

The returned Disposable unregisters it; the host disposes it for you on deactivate.

See also