TermAl

Feature Brief: Markdown Themes And Styles

Status

Phases 1–4 plus diagram-theme Override mode, Diagram look selection, and Diagram palette selection shipped. The Settings panel exposes a “Markdown” tab with five rows: Markdown theme, Markdown style, Diagram theme override (on / off), Diagram look (classic / handDrawn), and Diagram palette (match plus Mermaid’s five built-in presets — default / dark / forest / neutral / base). Selections persist across reloads (localStorage) and follow the same layout persistence pipeline as the UI theme preferences. Three Markdown themes (github-light, github-dark, terminal) and two Markdown styles (document, compact) ship alongside the match-ui default. Mermaid’s themeVariables palette is routed through the active Markdown theme when the palette is match; picking a specific Mermaid preset switches Mermaid’s theme field directly and skips TermAl’s overrides so the user sees the preset cleanly. When Override is on (default), author %%{init: …}%% directives and YAML frontmatter theme keys are stripped at render time so the user’s picks win uniformly. The Diagram look preference maps directly to Mermaid’s top-level look config field, with a fixed handDrawnSeed so the rough.js sketch is deterministic across re-renders. Complements the existing app-wide theme system described in ../themes.md.

Problem

Rendered Markdown currently inherits the app’s theme uniformly: the chrome and the document content share one palette, one chrome style, and one set of colors for code blocks, links, quotes, tables, Mermaid diagrams, and KaTeX math. That’s fine as a default, but it rules out a few reasonable preferences:

Today the only way to get any of these is to edit theme CSS by hand and rebuild. We already ship 18 palettes and 5 chrome styles for the workspace; Markdown deserves the same treatment for its own surfaces.

Goals

Non-goals (V1)

Product model

Preferences

Two new localStorage keys, mirroring the existing theme preferences:

Preference Storage key Default
Markdown theme termal-markdown-theme match-ui
Markdown style termal-markdown-style match-ui

match-ui is the default token that means “inherit from the active UI theme / UI style”. Explicitly selecting a Markdown theme or style overrides just the Markdown surface.

DOM application

Applied to the same <html> element as the existing theme attributes, so CSS selectors can cascade cleanly:

<html
  data-theme="warm-light"
  data-ui-style="theme-default"
  data-markdown-theme="github-light"
  data-markdown-style="document"
>

When the preference is match-ui, the attribute is omitted (or set to match-ui) and Markdown surfaces fall back to the same variables the UI theme provides. When it’s overridden, the Markdown preset file supplies its own variable overrides under [data-markdown-theme="github-light"] scoped to the Markdown roots.

Scoping

The Markdown theme applies to:

The Markdown theme does not apply to Monaco editors (they follow the UI theme to stay visually consistent with the surrounding workspace) or to workspace chrome.

What a preset controls

Axis Example variables
Typography --markdown-font-family, --markdown-heading-font-family, --markdown-heading-scale, --markdown-paragraph-leading
Headings --markdown-h1-weight, --markdown-h1-size, --markdown-h1-color, same for h2–h6
Links --markdown-link-color, --markdown-link-hover, --markdown-link-underline
Code --markdown-code-bg, --markdown-code-fg, --markdown-code-border
Code blocks --markdown-code-block-bg, syntax-highlighting class overrides
Quotes --markdown-quote-border, --markdown-quote-bg, --markdown-quote-color
Tables --markdown-table-border, --markdown-table-header-bg, --markdown-table-row-zebra
Lists --markdown-bullet-color, --markdown-list-indent
Mermaid themeVariables + themeCSS passed to mermaid.initialize
KaTeX text color, delimiter color for inline/block math wrappers

Style presets (the second axis) control layout-level treatments: line-length caps, spacing rhythm, heading separators, table density.

Initial preset catalogue

Curated small set for V1; each one is a CSS file under ui/src/themes/markdown/<id>.css plus a MARKDOWN_THEMES entry in ui/src/themes.ts.

Markdown themes:

Markdown styles:

Mermaid diagram theming

Mermaid has its own theme and style mechanism, independent from TermAl’s Markdown theme. A diagram author can pick one of Mermaid’s built-in theme presets (default, dark, forest, neutral, base) and can further customise individual palette variables, either via:

TermAl’s Markdown theme adds a third layer that the user controls from Settings. The three inputs have to compose in a predictable order.

Layering model

Precedence from lowest to highest when the user is in the default Override mode (see below):

  1. Mermaid’s built-in theme defaults. The fallback when no TermAl override applies. Historically this is default in light mode, dark in dark mode.
  2. Diagram author overrides (%%{init: …}%% or frontmatter). In Override mode these are stripped at render time so they do not reach Mermaid — the author’s theme / themeVariables choices are ignored for the reader’s rendering pass. In Respect mode these pass through and sit above the built-in defaults.
  3. TermAl’s Markdown theme overrides. When the user has picked a Markdown theme other than match-ui, the TERMAL_MERMAID_THEME_VARIABLES_BY_MARKDOWN_THEME lookup in ui/src/message-cards.tsx contributes palette variables (primaryColor, primaryBorderColor, lineColor, …) that align the diagram with the active prose theme. match-ui contributes no palette overrides and is a no-op by design.

The ordering is deliberate: the reader’s Markdown theme is the top layer in Override mode, so a diagram rendered on a dark prose background does not suddenly flash a light-theme diagram just because the original author wanted it that way in their deck. Authors who want their choices honoured can opt readers into Respect mode.

User control: Override vs. Respect

V1 defaults to Override mode: the user’s Markdown theme wins over author directives in the diagram source. Rationale:

Respect mode is the opt-in preference for users who are reviewing / authoring diagrams whose original styling matters — screenshots for a deck, diagrams being committed into docs that other tools will render, etc. Surfacing:

The preference is per-user, not per-document. Document-level opt-outs (e.g. a fence attribute like ` ```mermaid {respect} `) are a follow-up option if the single global switch proves too blunt.

Current implementation state

All three layers are active in the shipped code:

  1. Mermaid’s built-in defaults.
  2. Author directives (%%{init: …}%% or YAML frontmatter). Stripped at render time by applyActiveMermaidThemeOverride in ui/src/mermaid-theme-override.ts when the override toggle is on; passed through when off.
  3. TermAl’s Markdown-theme palette overrides via TERMAL_MERMAID_THEME_VARIABLES_BY_MARKDOWN_THEME in ui/src/message-cards.tsx.

The termal-diagram-theme-override preference (default on) sits alongside the Markdown theme and style preferences in the localStorage key set and the workspace-layout save payload. Its <html> attribute is data-diagram-theme-override="on" | "off", read from within the Mermaid render path without any React prop plumbing. Toggling the preference causes the next Mermaid render to pick up the new mode; existing rendered diagrams are not forced to re-render, which keeps the switch cheap.

The stripper is regex-based — it handles the common forms (%%{init: {...}}%% anywhere in the source, theme: scalar and themeVariables: / themeCSS: block keys in a leading YAML frontmatter fence) and is unit-tested in ui/src/mermaid-theme-override.test.ts. Complex edge-case YAML (anchors, flow-style mappings split across lines, tagged scalars) is explicitly out of scope for V1; if someone lands a diagram that exercises one of those we can swap in a real YAML parser.

Diagram palette

Mermaid ships five built-in theme presets:

TermAl’s previous behaviour auto-picked between default and dark based on Monaco appearance and layered Markdown-theme palette overrides on top. The new Diagram palette preference adds an explicit escape hatch:

The preference is keyed on termal-diagram-palette in localStorage, persisted through the workspace-layout save path, applied as data-diagram-palette on <html>, and read in message-cards.tsx::readActiveDiagramPalette at render time. The palette + look + theme-override + Markdown-theme axes are all orthogonal — any combination composes cleanly.

Diagram look

TermAl exposes two Mermaid render aesthetics through the top-level look config field:

The Diagram look axis is orthogonal to the Markdown theme and to the Diagram theme override. Any combination composes: e.g. github-light Markdown theme + handDrawn look produces a sketched diagram in GitHub-style blues; Override mode strips author look: directives the same way it strips author theme keys, so the user’s preference wins uniformly across the document.

Implementation notes

Non-goals (V1)

Cross-surface consistency

Interaction with existing systems

Phased delivery plan

Phase 1: Infrastructure

Phase 2: Settings UI

Phase 3: First presets

Phase 4: Mermaid + KaTeX integration

Testing

Open questions