
# Themes

A theme is a plain object: a name, a dark flag, a handful of chrome colors and a table of token
colors. Nothing here reads a CSS variable or a class name — every color a workbench or an editor
paints comes from the `Theme` object it was given.

## The `Theme` interface

```ts
interface TokenStyle {
  color?: string;
  fontWeight?: string;
  textDecoration?: string;
}

interface Theme {
  name: string;
  dark: boolean;
  bg: string;
  panel: string;
  line: string;
  fg: string;
  muted: string;
  accent: string;
  hover: string;
  selection: string;
  tokens: Record<string, TokenStyle>;
}
```

| Field       | What it colors                                                           |
| ----------- | ------------------------------------------------------------------------ |
| `name`      | What a theme picker lists, and what a host persists to remember a choice |
| `dark`      | Whether this is a dark theme — sets the page's color scheme              |
| `bg`        | The editor's background                                                  |
| `panel`     | Sidebar, panel, activity bar and status bar backgrounds                  |
| `line`      | Borders between panes, and the gutter separators                         |
| `fg`        | The default text color                                                   |
| `muted`     | Secondary text — descriptions, line numbers, placeholders                |
| `accent`    | Focus rings, active tabs, links, the current search match                |
| `hover`     | The background a row or a button takes under the pointer                 |
| `selection` | Selected text and selected rows — an opaque color, not a translucent one |
| `tokens`    | Syntax colors, keyed by token type                                       |

`selection` has to be opaque. A translucent fill would blend into the active line's background in
a live editor but sit flat in server-rendered markup, so the two would never quite match.

## `lightTheme` and `darkTheme`

Both built-ins are exported from `codelet` and from `codelet/workbench`:

```ts
import { lightTheme, darkTheme } from "codelet";
// or
import { lightTheme, darkTheme } from "codelet/workbench";
```

Use whichever entry you already import from. `codelet/workbench` also re-exports `fontFamily` and
`fontSize` — the shell's own typography, which follows the viewport rather than the theme, so it
isn't part of the `Theme` shape.

## The workbench

Pass `theme` to set the active theme, and `themes` for the set the reader may switch between:

```ts
import { Workbench, lightTheme, darkTheme } from "codelet/workbench";

const workbench = new Workbench({
  parent: document.getElementById("app")!,
  fs,
  theme: darkTheme,
  themes: [lightTheme, darkTheme],
  onTheme: (theme) => localStorage.setItem("theme", theme.name),
});
```

The theme control lives under the gear at the foot of the activity bar, and what `themes` holds
decides what it says:

| `themes`        | Gear menu                                                                    |
| --------------- | ---------------------------------------------------------------------------- |
| Omitted, or one | No theme row at all                                                          |
| Two             | `Theme: <the other one>`, which swaps them                                   |
| Three or more   | `Color Theme…`, the same picker as "Preferences: Color Theme" in the palette |

`onTheme` fires with the new theme every time the reader switches. `workbench.setTheme(theme)`
changes it back from your own code — a no-op if `theme` is already the active one.

## The standalone `Editor`

The same `theme` option and `setTheme` method work on `Editor`, without any of the activity bar
machinery — there's no chrome around a bare editor to put a picker in, so switching is entirely
up to your own UI:

```ts
import { Editor, lightTheme, darkTheme } from "codelet";

const editor = new Editor({ parent, doc, theme: darkTheme });

let dark = true;
toggleButton.onclick = () => {
  dark = !dark;
  editor.setTheme(dark ? darkTheme : lightTheme);
};
```

:read-more{to="/guide/workbench#the-standalone-editor"}

## `codelet/themes`

`lightTheme` and `darkTheme` are the only two built-ins in `codelet` and `codelet/workbench`.
`codelet/themes` is a separate entry with rangi's own theme table converted to this shape, so a
workbench that only wants the two built-ins never pays for the rest of it:

```ts
import { rangiThemes, fromRangi } from "codelet/themes";
import { lightTheme, darkTheme } from "codelet/workbench";

const themes = [lightTheme, darkTheme, ...rangiThemes];
```

`fromRangi(theme, name?)` converts a single rangi theme. `rangiThemes` is every theme rangi ships
that has plain colors rather than CSS variables, already converted and sorted by name.

## Writing your own theme

A `Theme` is just an object, so building one is filling in the fields:

```ts
import type { Theme } from "codelet";

const sunset: Theme = {
  name: "Sunset",
  dark: true,
  bg: "#1b1024",
  panel: "#150d1d",
  line: "#3a2a4a",
  fg: "#f1e6ff",
  muted: "#a68fc2",
  accent: "#ff8a5c",
  hover: "#ffffff14",
  selection: "#4a2f63",
  tokens: {
    kwd: { color: "#ff6b9d" },
    str: { color: "#ffd479" },
    num: { color: "#ffd479" },
    cmnt: { color: "#7d6a94" },
    func: { color: "#7ec8ff" },
    type: { color: "#7ec8ff" },
    class: { color: "#7ec8ff" },
    bool: { color: "#ff8a5c" },
    var: { color: "#f1e6ff" },
    oper: { color: "#ff8a5c" },
    esc: { color: "#ffd479" },
    err: { color: "#ff5c5c" },
    section: { color: "#ff6b9d", fontWeight: "bold" },
  },
};
```

`tokens` is keyed by rangi's own token names: `bool`, `class`, `cmnt`, `deleted`, `err`, `esc`,
`func`, `insert`, `kwd`, `num`, `oper`, `section`, `str`, `type` and `var`. A `TokenStyle` needs
only `color` — `fontWeight` and `textDecoration` are there for the rare token that wants bold or
underline on top of it, the way the built-in themes use `fontWeight` on `section`.

A token name you leave out is never wrong — it simply isn't colored, and that kind of syntax
renders in `fg` like plain text. Most themes cover the dozen or so that carry real weight
(`kwd`, `str`, `num`, `cmnt`, `func`, `type`) and leave the rest to the foreground.

::warning
Pass a stable theme object. The shell and the editor both cache the styles they build for a
theme on that object's identity — build a new theme object on every render and each one adds a
fresh style module to the page instead of reusing what's already there.
::
