Themes

Plugins can register full color palettes via buoy.addTheme(...). Once registered, a theme appears in Settings → Appearance alongside the built-ins, persists across restarts like any other theme choice, and participates in the .app.danger impersonation overlay used by /as.

A theme is a palette of CSS color values plus a little metadata. Buoy translates it into a [data-theme="..."] { --bg: ...; ... } block injected into a single <style> tag, and <html data-theme="..."> swaps onto your palette when the user picks it.

addTheme(spec)

buoy.addTheme({
  id: "midnight",                 // local id; auto-namespaced to plugin:<plugin-id>:midnight
  name: "Midnight",               // shown in the Appearance picker
  description: "Deep ink palette",// optional one-line description shown under the name
  colorScheme: "dark",            // "dark" | "light" — drives the browser's color-scheme
  colors: { /* 24 required keys, see below */ },
  danger: { /* optional 4 keys for /as overlay */ },
  swatch: { /* optional 4-color preview; derived from colors when omitted */ },
});

IDs

The id you supply is your local id only. Lowercase letters / digits / dashes, 1–32 characters. The registry namespaces it as plugin:<plugin-id>:<id> before persisting and applying — so you can never collide with a built-in (nord, dark, etc.) or with another plugin. The full namespaced id is what shows up in <html data-theme="..."> and what prefs.theme stores.

The <plugin-id> segment is the basename Buoy assigns when it scans your file (my-themes.tsxmy-themes). Aim for short, kebab-case ids — the full attribute string ends up roughly plugin:my-themes:midnight.

Colors

All 24 keys are required. Each must be a non-empty CSS color string (#rrggbb, rgb(...), rgba(...), etc.). The SDK rejects partial palettes at registration time — a missing key would otherwise leave the app falling through to the dark default mid-render, which looks broken.

KeyCSS variableUsed for
bg--bgApp background.
bgElevated--bg-elevatedCards, panels, modals.
bgInput--bg-inputText inputs, the command bar.
bgHover--bg-hoverHover states on rows, buttons, menu items.
border--borderDefault borders.
borderStrong--border-strongEmphasized borders (selected rows, focus rings).
text--textPrimary text color.
textMuted--text-mutedSecondary text, labels.
textSubtle--text-subtleDe-emphasized text (timestamps, captions).
accent--accentPrimary action color (selected tab, link).
accentBg--accent-bgTranslucent accent fill (selected row tint, pill bg).
error--errorError text and inline error pills.
green--greenHealthy / Ready status badges.
greenBg--green-bgTranslucent green fill.
yellow--yellowPending / Warning status.
yellowBg--yellow-bgTranslucent yellow fill.
red--redFailed / Error status, danger buttons.
redBg--red-bgTranslucent red fill.
orange--orangeCrashLoop / Terminating, mid-severity status.
orangeBg--orange-bgTranslucent orange fill.
gray--grayNeutral / Unknown status.
grayBg--gray-bgTranslucent gray fill.
blue--blueInformational badges (Completed, Bound).
blueBg--blue-bgTranslucent blue fill.

Look at any built-in theme block in src/App.css ([data-theme="nord"], [data-theme="gruvbox"], etc.) for concrete reference values.

Danger overrides (optional)

When /as impersonation is active Buoy adds a .app.danger class to the root, which re-tints four variables to a “this could destroy things” red. The base override built into the app is calibrated for dark backgrounds and reads poorly on light palettes — light-theme authors should supply their own.

Allowed keys (all optional, all CSS color strings):

KeyCSS variable
border--border
borderStrong--border-strong
bgElevated--bg-elevated
bgHover--bg-hover

If you omit danger, the built-in reddish-dark .app.danger rule applies. The top-border red glow Buoy renders during impersonation is always on — that lives outside the theme.

Swatch (optional)

The Settings picker shows a four-color preview swatch per theme. If you omit swatch, Buoy derives one from colors.bg / bgElevated / text / accent, which is usually fine. Supply an explicit swatch: { bg, surface, text, accent } if you want to show off colors that aren’t in the structural-bg slot — for example, a vivid accent that hides behind the muted --accent you actually use.

Full example

/// <reference path="./buoy-sdk.d.ts" />

export default (buoy: Buoy) => {
  buoy.meta({ name: "Midnight Theme Pack", version: "0.1.0" });

  buoy.addTheme({
    id: "midnight",
    name: "Midnight",
    description: "Deep navy palette, neon accents.",
    colorScheme: "dark",
    colors: {
      bg: "#050810",
      bgElevated: "#0d1220",
      bgInput: "#0a0f1c",
      bgHover: "#161e30",
      border: "#1a2238",
      borderStrong: "#2a3550",
      text: "#e5ecff",
      textMuted: "#8a96b8",
      textSubtle: "#525d7a",
      accent: "#6ea8ff",
      accentBg: "rgba(110, 168, 255, 0.18)",
      error: "#ff7a90",
      green: "#5bd6a0",
      greenBg: "rgba(91, 214, 160, 0.14)",
      yellow: "#ffd166",
      yellowBg: "rgba(255, 209, 102, 0.14)",
      red: "#ff7a90",
      redBg: "rgba(255, 122, 144, 0.14)",
      orange: "#ff9f6a",
      orangeBg: "rgba(255, 159, 106, 0.14)",
      gray: "#8a96b8",
      grayBg: "rgba(138, 150, 184, 0.12)",
      blue: "#6ea8ff",
      blueBg: "rgba(110, 168, 255, 0.14)",
    },
    danger: {
      border: "#5a2030",
      borderStrong: "#7a2c40",
      bgElevated: "#1f0d14",
      bgHover: "#2d1320",
    },
  });
};

Test it locally

  1. Save the plugin under your plugins dir (see Installation paths).
  2. Open Buoy or use Settings → Plugins → Reload to pick up the change.
  3. Open Settings → Appearance. Your theme appears with a small From <plugin name> tag under the description.
  4. Click it; the whole app re-tints. Reload Buoy to confirm the choice persists.
  5. Run /as some-user to flip on the impersonation overlay and verify your danger colors look right (or that the fallback reads acceptably if you skipped danger).
  6. Delete the plugin file; the picker shows a “Selected theme not loaded” hint card with a Revert to Dark button.

Compile errors

addTheme validates at registration time. The most common errors show up in Settings → Plugins under the offending plugin:

  • colors.<key> is required (non-empty string) — you missed a key. Compare against the table above.
  • colorScheme must be "dark" or "light" — typo or missing.
  • unknown danger.<key> key — allowed: border, borderStrong, bgElevated, bgHover — only those four keys are accepted.
  • duplicate id — each theme id must be unique within a plugin — you called addTheme twice with the same local id.

Edit this page on GitLab