Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/letstri/druk/llms.txt

Use this file to discover all available pages before exploring further.

Every color Druk renders — editor background, tab strip, status bar, git gutter marks, and every syntax token — comes from the active theme. A theme is a plain TypeScript object with two sections: ui for chrome colors and syntax for tree-sitter capture groups. Adding one means copying an existing file, replacing the palette, and registering the result in THEMES. It appears in the command palette automatically, with no other wiring needed.

Steps

1

Copy an existing theme file

src/themes/github-dark.ts is the most complete reference — it covers every ui key and a full syntax map. Copy it to src/themes/my-theme.ts:
cp src/themes/github-dark.ts src/themes/my-theme.ts
Then rename the exported constant and update the name field:
src/themes/my-theme.ts
import type { Theme } from './types'

/** Palette from https://example.com/my-palette-source */
export const myTheme: Theme = {
  name: 'My Theme',
  ui: { /* ... */ },
  syntax: { /* ... */ },
}
Cite the upstream palette source in the file header, as every built-in theme does. The GitHub Dark theme links to https://github.com/primer/github-vscode-theme; Catppuccin links to the Catppuccin spec; Everforest links to palette.md in the Everforest repository. The comment makes it easy to check a hex value against the upstream and to update the theme when the palette ships a new version.
2

Adjust the ui and syntax objects

Replace every hex value in ui and syntax with colors from your chosen palette.For ui, map the palette’s neutral surface tones to the background keys and its text tones to the foreground keys. Only currentLine and indentGuide are usually absent from published palettes — blend those from bg yourself (see Blending currentLine and indentGuide below).For syntax, follow the scheme’s own highlighting guide where one exists. Catppuccin ships a style guide; Everforest ships palette.md with per-group recommendations; Solarized’s vim colorscheme is the canonical reference for that palette. Matching those assignments is what makes the theme feel recognizable.
Using a published palette verbatim — exact hex codes, not approximations — and following the scheme’s highlighting guide is what makes a theme feel like the real thing. A theme that slightly adjusts the palette ends up in an uncanny middle ground: familiar enough to notice the difference but not faithful enough to satisfy users of the original.
The GitHub Dark theme is a complete example of what a finished ui + syntax pair looks like:
src/themes/github-dark.ts
import type { Theme } from './types'

/** Palette from https://github.com/primer/github-vscode-theme (Dark Default). */
export const githubDark: Theme = {
  name: 'GitHub Dark',
  ui: {
    bg: '#0d1117',
    panelBg: '#161b22',
    barBg: '#010409',
    statusBg: '#1f6feb',
    statusFg: '#ffffff',
    text: '#e6edf3',
    dim: '#8b949e',
    faint: '#6e7681',
    accent: '#58a6ff',
    activeTabFg: '#e6edf3',
    inactiveTabFg: '#8b949e',
    treeSelectedBg: '#193356',
    treeFocusBg: '#21262d',
    dirty: '#d29922',
    error: '#f85149',
    folder: '#e6edf3',
    cursor: '#e6edf3',
    scrollbar: '#484f58',
    gutter: '#484f58',
    currentLine: '#161b22',
    indentGuide: '#1a1e24',
    gitAdded: '#3fb950',
    gitModified: '#d29922',
    gitDeleted: '#f85149',
  },
  syntax: {
    'comment':           { fg: '#8b949e', italic: true },
    'keyword':           { fg: '#ff7b72' },
    'keyword.operator':  { fg: '#ff7b72' },
    'operator':          { fg: '#ff7b72' },
    'string':            { fg: '#a5d6ff' },
    'escape':            { fg: '#79c0ff' },
    'number':            { fg: '#79c0ff' },
    'boolean':           { fg: '#79c0ff' },
    'constant':          { fg: '#79c0ff' },
    'function':          { fg: '#d2a8ff' },
    'constructor':       { fg: '#d2a8ff' },
    'type':              { fg: '#ff7b72' },
    'namespace':         { fg: '#79c0ff' },
    'variable':          { fg: '#ffa657' },
    'variable.builtin':  { fg: '#79c0ff' },
    'variable.member':   { fg: '#e6edf3' },
    'property':          { fg: '#79c0ff' },
    'attribute':         { fg: '#79c0ff' },
    'tag':               { fg: '#7ee787' },
    'label':             { fg: '#ff7b72' },
    'punctuation':       { fg: '#8b949e' },
    'punctuation.special': { fg: '#79c0ff' },
    'embedded':          { fg: '#e6edf3' },
    'error':             { fg: '#f85149' },
    'markup.heading':    { fg: '#79c0ff', bold: true },
    'markup.strong':     { fg: '#e6edf3', bold: true },
    'markup.italic':     { fg: '#e6edf3', italic: true },
    'markup.raw':        { fg: '#a5d6ff' },
    'markup.link':       { fg: '#a5d6ff' },
    'markup.link.label': { fg: '#79c0ff' },
    'markup.link.url':   { fg: '#a5d6ff', underline: true },
    'markup.list':       { fg: '#ff7b72' },
    'markup.quote':      { fg: '#8b949e', italic: true },
  },
}
3

Register the theme in src/themes/index.ts

Add an import and an entry to THEMES:
src/themes/index.ts
import { myTheme } from './my-theme'

// ...

export const THEMES = {
  // ... existing entries ...
  'my-theme': myTheme,
}
The key is what users type in the command palette to select the theme — keep it lowercase and hyphen-separated. The name field on the theme object is the human-readable label the palette displays.Restart Druk (or rebuild if running from source). Open the command palette (F1), type appearance or your theme’s name, and select it from the list.

Theme structure

A Theme has two top-level fields: ui and syntax.

ui — chrome colors

ui is a ThemeUi object. Every key must be present; the TypeScript type enforces this at compile time. The keys group into a few categories: Backgrounds
KeyWhat it paints
bgMain editor background
panelBgFloating panels and overlays (must stay opaque)
barBgTab strip and title bars
statusBgStatus bar background
Text
KeyWhat it paints
textPrimary foreground — file content, active labels
dimSecondary text — inactive tabs, line numbers
faintTertiary — scrollbar, gutter marks
statusFgStatus bar text
Accents and interactive
KeyWhat it paints
accentHighlighted links, focused borders
activeTabFgActive tab label
inactiveTabFgInactive tab label
treeSelectedBgSelected row in the file tree (focused)
treeFocusBgSelected row in the file tree (unfocused)
cursorEditor cursor color
scrollbarScrollbar thumb
gutterLine number column
folderFolder icon color
Highlights
KeyWhat it paints
currentLineBackground fill on the line the cursor is on
indentGuideIndent guide column tint
Status
KeyWhat it paints
dirtyUnsaved-change indicator, warning tint
errorError indicator, error tint
gitAddedGit added-line mark
gitModifiedGit modified-line mark
gitDeletedGit deleted-line mark

syntax — token colors

syntax maps tree-sitter capture group names to style objects. Each style can have fg (hex color string), bg, bold, italic, and underline fields — all optional. You don’t need to list every capture group. Sub-scopes fall back to their parent: if type.builtin isn’t defined, Druk uses the style for type instead. Omitting a group entirely costs nothing — it simply renders unstyled. The capture groups the bundled themes cover: comment, keyword, keyword.operator, operator, string, escape, number, boolean, constant, function, function.method, function.builtin, constructor, type, type.builtin, namespace, variable, variable.builtin, variable.member, property, attribute, tag, label, punctuation, punctuation.bracket, punctuation.delimiter, punctuation.special, embedded, error, and the markup.* family.

Derived surface colors

UiColors extends ThemeUi with three keys Druk computes automatically — you never set these in your theme file:
  • sidebarBg — set to panelBg normally, or 'transparent' when the transparent setting is on.
  • solidBg — always the theme’s own bg, regardless of transparency. Used when blending diagnostic tint colors, where a mix with "transparent" is not a valid color.
  • solidBarBg — always the theme’s own barBg, for the same reason.

Blending currentLine and indentGuide

Published palettes almost never name these two tones. Blend them off bg by hand — a small step toward the nearest mid-tone works well for most palettes:
// A very slight lift from bg — barely perceptible, just enough to show the cursor row.
currentLine: '#1a1f2b',  // bg is '#141720'; blend ~5% toward a mid-grey

// Even more subtle — should read as a hint, not a visible grid line.
indentGuide: '#181c28',
The built-in test suite checks that currentLine and indentGuide are distinct from bg — they must not be identical, or the cursor row and indent stops become invisible.

How setTheme() works

setTheme() replaces syntaxTheme rather than merging into it. This is intentional: themes don’t all define the same capture groups, and a leftover group from the previous theme would render in the wrong palette — near-invisible text when the switch flips from light to dark or vice versa.
src/themes/index.ts
export function setTheme(name: ThemeName): void {
  painted = name
  setUi(colorsFor(name, seeThrough))
  // Replace, never merge.
  for (const group of Object.keys(syntaxTheme)) delete syntaxTheme[group]
  Object.assign(syntaxTheme, THEMES[name].syntax)
}
Sub-scopes fall back to their parent anyway, so a group your theme omits is cleanly absent rather than stale.

ui is a Solid store — syntaxTheme is a plain object

ui is created with createStore. Solid components never re-render on their own — reading ui.bg inside JSX is what subscribes that render spot to future changes. If ui were a plain object, every color on screen would become stale after a theme switch and never update. syntaxTheme, by contrast, is a plain object. It is only read when the syntax style table is rebuilt (on theme switch and on first paint), so reactivity adds no value there.
Never replace ui with a plain object or try to assign it wholesale. Always go through setUi (the Solid store setter). A plain object swap leaves every component that ever read a ui.* key frozen at the old color.

Indent guides

Indent guides ride the same pipeline as syntax highlights: computeHighlights appends one indent.guide capture per indent stop, and the style for that capture comes from ui.indentGuide via the syntax style table. There is no separate color key to set — indentGuide in ui covers it.

Registration example

A complete registration, as it would appear in src/themes/index.ts:
src/themes/index.ts
import { myTheme } from './my-theme'

// ... other imports ...

export const THEMES = {
  'dark':           githubDark,
  'light':          githubLight,
  // ... other themes ...
  'my-theme':       myTheme,
}
Once registered, the theme appears in the Appearance submenu of the command palette. No restart is needed when running from source with hot-reload; a compiled binary requires a rebuild.

Build docs developers (and LLMs) love