Themes
Status: the add-on framework described here shipped 2026-08-26 (P1). Everything below works except Install Theme… and
.marginalthemedouble-click packages, which are planned.
Marginal ships with ten built-in themes — five light/dark pairs (Marginal, GitHub, Solarized, Zenburn, Dracula). You pick one theme per slot in Settings ▸ Appearance: the Light theme is used in light appearance, the Dark theme in dark appearance, and “System” follows your OS. This guide is about adding your own themes beyond the built-ins.
A theme controls the whole app in one file: window chrome (sidebar, toolbar, panels), the editor (colors, cursor, selection, markdown syntax tints), the preview (text, links, headings, tables, code blocks), mermaid diagrams, and what Print/PDF/HTML export looks like (exports always use your light-slot theme, so a light theme doubles as your “paper” style).
Installing a theme
Section titled “Installing a theme”-
Open Settings (⌘, / Alt+,) ▸ Appearance and click Open Themes Folder. This opens (and creates, on first use) the folder where add-on themes live:
OS Folder macOS ~/Library/Application Support/md.marginal/themes/Windows %APPDATA%\md.marginal\themes\Linux ~/.config/md.marginal/themes/ -
Copy the theme in — either a folder like
nightlily/containing atheme.json, or a baresome-theme.jsonfile. -
Click Reload Themes (or just reopen Settings — it rescans on open). The theme appears in the Light or Dark dropdown under an Add-ons heading, depending on its
appearance. -
Select it. Done — no restart needed.
If a theme does not appear, Settings shows a “theme files failed to load” notice listing the file and the reason (see Troubleshooting below). Nothing is ever skipped silently.
Uninstalling: delete the theme’s folder/file and Reload Themes. If the removed theme was selected, that slot falls back to the built-in default (Marginal Light / Marginal Dark) automatically.
Sharing: a theme is its folder — zip it, send it, the other person
drops it in their themes folder. (A double-clickable .marginaltheme
package is planned; the unzipped folder is always the installed form.)
Making your own theme
Section titled “Making your own theme”The package
Section titled “The package”A theme is a folder in the themes directory:
themes/ nightlily/ theme.json # required — everything below lives here syntax.tmTheme # optional — custom code-block colors (TextMate format)If you don’t need a custom .tmTheme, a single nightlily.json file
directly in themes/ works too. The folder name doesn’t matter; the theme’s
identity is the id inside theme.json.
theme.json — the minimum
Section titled “theme.json — the minimum”Five fields are required; everything else is optional and gets sensible defaults derived from these:
{ "id": "nightlily", "name": "Night Lily", "appearance": "dark", "palette": { "bg": "#211d24", "fg": "#e6e1ea", "muted": "#9a92a5", "chromeBg": "#2a2530", "border": "#3d3646", "hoverBg": "rgb(255 255 255 / 0.05)", "activeBg": "rgb(255 255 255 / 0.09)", "accent": "#c792ea", "accentFg": "#211d24", "selectionBg": "#44395c", "error": "#ef6b73", "warning": "#d19a66", "success": "#98c379" }, "syntect": "base16-ocean.dark"}-
id— lowercase kebab-case, unique, and stable: it’s what your settings remember. Don’t reuse a built-in id (marginal-*,github-*,solarized-*,zenburn-*are taken). -
name— what the dropdown shows. -
appearance—"light"or"dark"; decides which slot dropdown lists the theme and pairs it with the matching preview base style. -
palette— the 13 chrome colors, all required:Token Where you see it bg/fgpage background / primary text mutedsecondary text — captions, file details, placeholders chromeBgtoolbar, sidebar, file list, panels, dialogs border1px hairlines between panes and around controls hoverBgwash behind hovered buttons/rows (translucent works best) activeBgwash behind pressed/selected controls accentprimary buttons, links, focus rings, the unsaved-changes dot accentFgtext on top of accentselectionBgtext selection error/warning/successstatus colors -
syntect— the code-block color scheme. Either one of the bundled names —"InspiredGitHub","Solarized (light)","Solarized (dark)","base16-ocean.light","base16-ocean.dark","base16-eighties.dark","base16-mocha.dark","dracula"— or exactly"./syntax.tmTheme"to use the TextMate theme file sitting next to yourtheme.json. (Any editor theme exported as.tmThemeworks — a huge ecosystem to borrow from.)
That minimal theme is already complete: editor colors are derived from the code scheme, markdown tints and preview colors from the palette.
Optional: overrides
Section titled “Optional: overrides”Add an "overrides" object when the derived look isn’t enough:
"overrides": { "editor": { "bg": "#211d24", "fg": "#e6e1ea", "selection": "#44395c", "cursor": "#c792ea", "activeLine": "rgb(255 255 255 / 0.04)", "gutterFg": "#5f5769" }, "syntax": { "heading": "#c792ea", "heading-weight": "600", "heading1-size": "1.9em", "heading2-size": "1.4em", "heading3-size": "1.15em", "heading4-size": "1.07em", "link": "#82aaff", "punctuation": "#7f88a6", "tag": "#82aaff", "frontmatter-key": "#e0af68", "frontmatter-value": "#9ece6a" }, "preview": { "link": "#82aaff", "headingFg": "#c792ea", "border": "#3d3646", "blockquoteBorder": "#82aaff", "blockquoteFg": "#9a92a5", "codeBg": "#2a2530", "inlineCodeBg": "#332d3a", "tableStripe": "rgb(255 255 255 / 0.04)" }, "mermaid": { "primaryColor": "#2a2530", "primaryTextColor": "#e6e1ea" }}editor— the editing pane’s chrome when you don’t want the code scheme’s own:bgfggutterFgactiveLineselectioncursor.syntax— tints for markdown as you type (and they win over the code scheme’s projection). Useful keys:heading(all levels),heading1…heading6(per level),heading-weight,heading1-size…heading4-size(em values scale editor headings; the built-ins run 1.9 / 1.4 / 1.15 / 1.07em),link,punctuation(the#/-/fence marks),tag, plus code-token colors likekeyword,string,comment,function,number. YAML frontmatter has its own pair,frontmatter-keyandfrontmatter-value, for the block at the top of a document: unset, keys take the heading colour and values read as ordinary body text, so they already tell apart in every theme; set either to choose your own. The block is sized atheading4-size, so it sits just above body text in a theme that scales headings and matches it in one that doesn’t. A key is a line’s leadingname:(indented ones too, so nested maps colour as keys); list items, block scalars and the wrapped continuation lines of a long value count as value text.preview— the rendered pane:link,headingFg,heading1Fg(H1 only, layered overheadingFg),border(h1/h2 underlines, hr, table borders),blockquoteBorder,blockquoteFg,codeBg(fenced blocks),inlineCodeBg,tableStripe.mermaid— diagram theme variables passed straight to mermaid (e.g.primaryColor,lineColor); the special key"theme": "default"or"dark"pins a stock mermaid look instead. No alpha colors here — mermaid’s color math can’t handlergb(… / 0.5).
Value rules
Section titled “Value rules”- Colors: hex (
#rrggbb, with alpha#rrggbbaa),rgb(…)/rgb(r g b / a),hsl(…), or CSS named colors. Max 64 characters. - For safety, values may only contain letters, digits, and
# % ( ) , . / -plus spaces. Anything else —;, braces, quotes,<,>— rejects the whole theme (you’ll see it in the diagnostics list with the field name). - Limits:
theme.json≤ 64 KB,.tmTheme≤ 1 MB, up to 100 add-on themes. - Optional
"schema": 1field: leave it out or set 1; a future format bump will use it so old apps skip themes they can’t render rather than mangling them.
Iterating
Section titled “Iterating”- Edit
theme.json, save. - Settings ▸ Reload Themes — changes apply immediately, including to the open document.
- Check both panes (editor + preview), a document with code blocks, tables,
a blockquote, and a mermaid diagram; then Print (⌘P/Alt+P) to see the
export look if your theme is in the light slot.
docs/QA.md’s fixtures (DEMO.md) exercise everything at once. - Tip for dark themes: also check Force light preview users — your dark
theme’s editor will sit beside the light-slot preview, so its
accentandselectionBgshould hold up next to a white pane.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| Theme not in the dropdown | Wrong folder; or it failed to load — check the diagnostics notice in Settings |
| “id collides with a built-in” | Rename your id (built-in prefixes are reserved) |
| “value rejected” naming a field | The value uses a forbidden character or is over 64 chars — see Value rules |
| Theme listed under the wrong dropdown | appearance says the other mode |
| Code blocks ignore my colors | syntect names a stock theme instead of "./syntax.tmTheme", or the sidecar failed to parse (diagnostics will say) |
| Mermaid diagrams look off | Alpha color in overrides.mermaid, or pin "theme": "dark" instead of variables |
| Everything fell back to Marginal defaults | The selected theme was removed or failed to load — reinstall or reselect |
For the engineering design behind all of this (loading rules, security model, phasing), see docs/THEME_ADDONS.md.