Customize

Themes

Switch themes with the picker or the config file, list the bundled themes, and write and install a theme of your own.

Press t in View, Ask, Choose, or any screen built on DocumentScreen to open the theme picker. Type to filter the list. Move with up and down, and the screen behind the picker previews each theme as you move. Enter keeps the highlighted theme and saves it to ~/.config/tooee/config.json, so the next Tooee app starts with it. Escape restores the theme you started with. The status bar shows the current theme after Theme:.

To set a theme without the picker, name it in a config file:

~/.config/tooee/config.json
{
  "theme": { "name": "catppuccin", "mode": "dark" }
}

mode picks the light or the dark colours of the theme. The picker changes name and leaves mode as it is. Configuration covers the file locations and the layers.

Bundled themes

Tooee ships 34 themes. Each name is a file under packages/themes/themes/ in the OpenCode JSON format. The two right-hand columns name the Hunk palette that draws diffs for the theme in each mode; the Diff palettes section explains the mapping.

ThemeDark diff paletteLight diff palette
auralaserwavemin-light
ayuayu-darkayu-light
catppuccincatppuccin-mochacatppuccin-latte
catppuccin-frappecatppuccin-frappecatppuccin-latte
catppuccin-macchiatocatppuccin-macchiatocatppuccin-latte
cobalt2dark-pluslight-plus
cursorvitesse-darkvitesse-light
draculadraculamin-light
everforesteverforest-darkeverforest-light
flexokivitesse-darkvitesse-light
githubgithub-darkgithub-light
github-lightgithub-darkgithub-light
gruvboxgruvbox-dark-mediumgruvbox-light-medium
kanagawakanagawa-wavekanagawa-lotus
lucent-orngvespermin-light
materialmaterial-themematerial-theme-lighter
matrixvitesse-blackmin-light
mercurymin-darkmin-light
monokaimonokaimin-light
nightowlnight-owlnight-owl-light
nordnordmin-light
one-darkone-dark-proone-light
opencodevitesse-darkvitesse-light
opencode-lightvitesse-darkvitesse-light
orngvespermin-light
osaka-jadeeverforest-darkeverforest-light
palenightmaterial-theme-palenightmaterial-theme-lighter
rosepinerose-pinerose-pine-dawn
solarizedsolarized-darksolarized-light
synthwave84synthwave-84min-light
tokyonighttokyo-nightmin-light
vercelvitesse-blackvitesse-light
vespervespermin-light
zenburngruvbox-dark-softgruvbox-light-soft

aura, ayu, github-light, and opencode-light define one set of colours, so mode changes nothing for them. The other thirty carry a dark and a light value for each colour.

Write a theme

~/.config/tooee/themes/deploy-dark.json
{
  "$schema": "https://opencode.ai/theme.json",
  "defs": {
    "ink": "#1b1f27",
    "paper": "#e6e9ef",
    "amber": "#f5a742",
    "moss": "#7fd88f",
    "rose": "#e06c75"
  },
  "theme": {
    "primary": "amber",
    "accent": "amber",
    "error": "rose",
    "success": "moss",
    "text": { "dark": "paper", "light": "ink" },
    "textMuted": "#8a90a0",
    "background": { "dark": "ink", "light": "paper" },
    "backgroundPanel": { "dark": "#232834", "light": "#d9dde5" },
    "backgroundElement": { "dark": "#2c3240", "light": "#cdd2db" },
    "border": "#3c4250",
    "syntaxKeyword": "amber",
    "syntaxString": "moss"
  }
}

The file name is the theme name, so this file adds deploy-dark to the picker. theme maps Tooee colour keys to values. A value is one of:

ValueMeaning
"#f5a742"A hex colour.
"amber"The name of an entry in defs, or of another key in theme.
{ "dark": "ink", "light": "paper" }One value per mode. theme.mode in the config picks one.
"transparent" or "none"No colour.

A key you leave out takes a fallback: #808080 for most colours, #1e1e1e for the backgrounds, #d4d4d4 for text, markdownText, and markdownCodeBlock. cursorLine falls back to backgroundElement and selection to backgroundPanel, so set those two backgrounds when you set background. A name that refers back to itself resolves to #808080.

GroupKeys
UIprimary, secondary, accent, error, warning, success, info, text, textMuted, background, backgroundPanel, backgroundElement, border, borderActive, borderSubtle
CursorcursorLine, selection
SearchsearchMatchBg, searchCurrentMatchBg, searchMatchFg, searchCurrentMatchFg
MarkdownmarkdownText, markdownHeading, markdownLink, markdownLinkText, markdownCode, markdownBlockQuote, markdownEmph, markdownStrong, markdownHorizontalRule, markdownListItem, markdownListEnumeration, markdownImage, markdownImageText, markdownCodeBlock
SyntaxsyntaxComment, syntaxKeyword, syntaxFunction, syntaxVariable, syntaxString, syntaxNumber, syntaxType, syntaxOperator, syntaxPunctuation
DiffdiffAdded, diffRemoved, diffContext, diffHunkHeader, diffHighlightAdded, diffHighlightRemoved, diffAddedBg, diffRemovedBg, diffContextBg, diffLineNumber, diffAddedLineNumberBg, diffRemovedLineNumberBg

The Search keys colour search matches in a row document. searchMatchBg is the row background of every match, and searchCurrentMatchBg is the row background of the current match. searchMatchFg and searchCurrentMatchFg colour the gutter signs of those rows. Every bundled theme sets all four. A theme that leaves them out gets derived values:

KeyDerived value
searchMatchBg22% of warning over background
searchCurrentMatchBg35% of primary over background
searchMatchFgwarning
searchCurrentMatchFgprimary

Over a transparent background, the derived backgrounds are translucent.

The Diff keys feed the diff syntax scopes. OpenTUI bundles no diff grammar and Hunk draws diffs from its own palette, so these keys have no visible effect.

Install a theme

Put the file in ~/.config/tooee/themes/ to have it in every app, or in .tooee/themes/ inside a project. Tooee reads the bundled folder, then the global folder, then .tooee/themes/ in each directory from the filesystem root down to the working directory. A later file with the same name replaces an earlier one: a dracula.json in your global folder replaces the bundled Dracula, and a project file replaces a global one. Between two project folders, the one nearest to the working directory wins, as it does for config files. The folders are read once per process; restart the app after you add a file.

list-themes.ts
import { getThemeNames } from "@tooee/themes";

console.log(getThemeNames().join("\n"));

bun list-themes.ts prints the sorted names, with deploy-dark between cursor and dracula once the file above is in place. loadThemes() returns the parsed JSON of each theme by name.

Use the theme in your components

theme-cycle.tsx
import { useCommand } from "@tooee/commands";
import { AppLayout } from "@tooee/layout";
import { launchCli, useQuitCommand, useThemeCommands } from "@tooee/shell";
import { useTheme, useThemeSwitcher } from "@tooee/themes";
import type { ReactNode } from "react";

const Swatches = function Swatches(): ReactNode {
  const { theme, name, mode } = useTheme();
  const { nextTheme } = useThemeSwitcher();
  useQuitCommand();
  useThemeCommands();
  useCommand({ handler: nextTheme, hotkey: "n", id: "theme.next", title: "Next theme" });

  return (
    <AppLayout
      statusBar={{
        items: [
          { label: "Theme:", value: `${name} ${mode}` },
          { label: "", value: "t pick  n next  q quit" },
        ],
      }}
    >
      <box flexDirection="column" paddingLeft={1}>
        <text content="primary" fg={theme.primary} />
        <text content="accent" fg={theme.accent} />
        <text content="error" fg={theme.error} />
        <text content="success" fg={theme.success} />
        <text content="textMuted" fg={theme.textMuted} />
      </box>
    </AppLayout>
  );
};

await launchCli(<Swatches />);

n steps to the next theme in the sorted list and t opens the picker. Both save the theme to the global config file.

HookPackageReturns
useTheme()@tooee/themes{ theme, syntax, name, mode }. theme is a ResolvedTheme: every key from the table above as a hex string.
useThemeSwitcher()@tooee/themesThe same fields plus setTheme(name, { persist }), setMode(mode, { persist }), nextTheme(), prevTheme(), and allThemes.
useThemePicker()@tooee/shell{ isOpen, open, close, confirm, preview, entries, currentTheme, originalTheme } for the picker overlay.
useThemeCommands()@tooee/shellRegisters t with the id cycle-theme and returns { name, picker }. Pass { enabled: false } to keep the live name without the key.

TooeeProvider mounts ThemeSwitcherProvider with the theme from the config, so every hook works in an app started with launchCli or runCliSession. DocumentScreen calls useThemeCommands for you unless themeCommands is false; a screen of your own calls it to get t. nextTheme, prevTheme, and setTheme with persist: true write the global config file, as Enter in the picker does; setTheme(name) alone changes the screen and writes nothing. setMode("light") or setMode("dark") switches the current theme to that variant while the app runs, and persist: true also writes the mode to the global config file. useThemePicker lives in @tooee/shell; @tooee/themes exports the loading and context APIs only. The colour props on <text> and <box> belong to OpenTUI.

Syntax colours

The syntax* and markdown* keys build the SyntaxStyle that colours code blocks and code files; useTheme().syntax returns it for a code element of your own. Colours appear for the four grammars OpenTUI bundles, typescript, javascript, markdown, and zig. View: content formats covers the rule.

Diff palettes

Hunk accepts no colour table, so a diff is drawn with one of Hunk's bundled palettes. resolveHunkDiffTheme(name, theme) from @tooee/diff looks the theme name up in the table at the top of this page and returns the dark or the light entry by the brightness of theme.background. A name that is not in the table, such as deploy-dark, gets github-dark or github-light on the same side. A file of yours that replaces a bundled theme keeps that theme's row. HUNK_THEME_MAP exports the table.

Next

On this page