Skip to content

Writing Themes ​

A theme is a folder with a theme.toml:

solar/
  theme.toml        name, colour scheme, variables, element styles
  theme.css         optional: decoration theme.toml cannot express
  fonts/…           optional: fonts the theme uses
  paper.png         optional: images
  solar-dark.json   optional: a syntax-highlighting theme

theme.toml works in two layers:

  1. [vars] — about thirty variables: fonts, sizes, page width and colours. Most themes only need these.
  2. [elements] — how individual elements look: heading sizes and rules, the quote style, code blocks, tables, links, lists.

Every theme starts from Typora's default look; anything you leave out stays as it is. On top of that, [chrome] recolours the window, [[fonts]] loads bundled fonts, [syntax] picks code colours, and css adds a stylesheet for anything else.

Because the format is structured, Typora can carry the same theme into the editor, HTML and PDF export, Word export (as Word styles) and its layout calculations for long documents. That is the reason to prefer theme.toml over CSS wherever it can say what you want.

A first theme ​

toml
#:schema https://docs.typora.io/schema/theme-1.json
name = "Solar"
color-scheme = "light"

[vars]
body-font = 'Charter, Georgia, serif'
font-size = "17px"
line-height = 1.7
content-width = "700px"
bg = "#fdf6e3"
text = "#586e75"
heading = "#073642"
accent = "#b58900"
border = "#e6dfc8"

[elements.blockquote]
style = "bar"
bar-color = "$accent"

[elements.h1]
rule = "below"

Save it as Themes/solar/theme.toml (Settings ▸ View ▸ Open Folder) and choose Solar. Typora reloads it when you switch back to the app.

Editor support

The #:schema line lets editors with a TOML schema plugin — Even Better TOML in VS Code, for example — complete and check every key.

Metadata ​

KeyValue
nameThe name shown in menus and Settings. Defaults to the folder name.
color-scheme"auto", "light" (default) or "dark". An auto theme has light and dark values and follows the appearance — the system's, live, when the user's Appearance is Auto. A light or dark theme switches the whole app to that scheme while it is in use.
author, version, descriptionOptional.
cssA stylesheet in the theme folder, applied after the theme. See Theme CSS.

Values ​

  • Lengths are strings with a unit: "16px", "1.2em", "2rem", "0". A bare number is refused, because margin = 1.2 would not say whether that is em or px.
  • Numbers without a unit: line-height = 1.6, weight = 700.
  • Colours are any CSS colour: "#b58900", "rgb(32 43 51 / 0.6)", "color-mix(in srgb, $accent 20%, white)".
  • $name refers to a variable: a built-in one ($accent, $text) or one you define in [vars]. References stay live, so a colour that differs between light and dark follows the appearance.
  • Files are paths inside the theme folder: "paper.png", "fonts/charter.woff2".

[vars] ​

VariableTypeSets
body-fontfontBody text font
heading-fontfontHeading font (defaults to the body font)
mono-fontfontCode font
font-sizelengthBody text size. The user's Editor Font Size setting still wins.
font-weightnumberBody text weight, e.g. 300 for a light face
line-heightnumberBody line height
letter-spacinglengthBody tracking
content-widthlengthWidth of the text column
paragraph-spacinglengthSpace between paragraphs
text-alignleft / justifyParagraph alignment
bgcolourPage background
bg-imagefilePage background image (it scrolls with the document)
textcolourBody text
headingcolourHeadings
mutedcolourSecondary text: h4–h6, list numbers, metadata
accentcolourThe accent: links, checkboxes, the default quote bar, the window's accent
linkcolourLinks (defaults to the accent)
syntax-markercolourMarkdown markers shown while editing (**, #)
surfacecolourSoft surfaces: table headers, callouts
surface-hovercolourHovered surfaces
code-bgcolourCode block background
inline-code-bgcolourInline code background
bordercolourRules and hairlines: dividers, tables, code outlines
highlightcolour==highlight== (keep it translucent so selection shows through)
selectioncolourText selection
block-selectioncolourSelected blocks
caretcolourThe caret
delcolourDeleted text
placeholdercolourPlaceholder text
alert-note, alert-tip, alert-important, alert-warning, alert-cautioncolourGitHub alert colours

Any other name you add is your own variable: use it as $name in the theme, or as var(--theme-name) in theme CSS.

Light and dark ​

[vars] applies in every appearance. An auto theme puts the values that differ in [vars.light] and [vars.dark]:

toml
color-scheme = "auto"

[vars]
accent = "#b58900"

[vars.light]
bg = "#fdf6e3"
text = "#586e75"

[vars.dark]
bg = "#002b36"
text = "#93a1a1"

Without a [vars.dark] value, dark mode keeps Typora's own dark colours. [elements] and [chrome] tables take .light / .dark sub-tables the same way, but referencing variables (bar-color = "$accent") is usually simpler.

[elements] ​

Headings ​

[elements.headings] sets all levels at once; [elements.h1] … [elements.h6] set one level and win over it.

KeyTypeIn
sizelengthh1–h6
marginlength or [top, bottom]h1–h6
fontfontheadings
weightnumberboth
colorcolourboth
alignleft / centerboth
transformnone / uppercaseboth
italictrue / falseboth
line-heightnumberboth
letter-spacinglengthboth
rulenone / below / shorth1–h6: a line under the heading across the column, or a short centred stroke
rule-colorcolourboth
toml
[elements.headings]
weight = 400
letter-spacing = "0"

[elements.h1]
size = "2.25em"
margin = ["1.2em", "0.6em"]
rule = "below"

[elements.h2]
align = "center"
rule = "short"

Quotes, code, tables and the rest ​

ElementKeys
blockquotestyle = card (default: a soft card with a quote mark) / bar (a bar down the left) / plain; bar-width, bar-color, color, bg, italic, indent
code-blockstyle = card (default) / outline (no fill, a hairline) / flat (square corners); bg, color, border, radius, font-size, indent
inline-codebg, color, border, radius
tablestyle = card (default: rounded outline, row rules) / grid (every cell ruled) / lines (row rules only) / plain (only the header rule); border, header-bg, header-transform (none / uppercase), stripe (background of every other row)
linkcolor, underline = always / hover / none
strongcolor, weight
hrcolor, thickness
listbullet = disc / circle / square (one shape at every level), marker-color
toml
[elements.blockquote]
style = "bar"
bar-width = "3px"
italic = true

[elements.table]
style = "lines"
header-transform = "uppercase"
stripe = "#f6f1e1"

[chrome] — the window ​

KeySets
sidebar-bgThe window background behind the sidebar and panels. Setting it turns off the translucent system material on macOS and Windows.
page-bgThe editor area (defaults to bg)
window-bgA CSS colour or gradient behind the whole workbench (no URLs)
detail-bgInspector background (defaults to sidebar-bg)
surface-radiusCorner radius of the editor and inspector cards
surface-shadowCard shadow: sm, md, lg, none or a CSS shadow
surface-borderInset card outline colour; does not consume layout space
surface-insetSpace around the editor/inspector group; the sidebar keeps its native titlebar alignment
surface-gapSpace between the editor and the visible inspector; removed when the inspector closes
tree-selected-radiusSelected file row radius in the card layout
text, text-muted, text-faintSidebar and panel text
divider, borderHairlines between panels
accentWindow accent (defaults to the theme's accent)
hover-bg, active-bgHovered and pressed rows
panel-bg, panel-borderMenus, the command palette, popovers
tree-fg, tree-dir-fg, tree-iconFile tree files, folders, icons
tree-selected-bg, tree-selected-fgThe open file's row
tree-selected-shadowA shadow under the open file's row: "sm", "md", "lg", "none" or any CSS shadow
toml
[chrome]
sidebar-bg = "#eee8d5"
tree-selected-bg = "#ffffff"
tree-selected-fg = "#073642"
tree-selected-shadow = "sm"

Setting window-bg, detail-bg or any surface-* key enables the optional card layout. Unset geometry defaults to zero, shadows to none, and the window background to sidebar-bg. These settings support .light / .dark tables. Themes without these keys retain the platform's existing appearance.

toml
[chrome]
window-bg = "linear-gradient(150deg, #e7dff4, #f9e5e3, #ffecd4)"
sidebar-bg = "transparent"
page-bg = "#ffffff"
detail-bg = "#ffffff"
surface-radius = "20px"
surface-shadow = "sm"
surface-inset = "12px"
surface-gap = "12px"

Cards keep the editor and inspector side by side. The titlebar controls, sidebar width, split direction and available buttons remain owned by the application. Use these chrome keys for shell spacing; document spacing still belongs in [vars] and [elements].

[[fonts]] ​

Themes bundle their fonts; Typora does not load fonts from the internet.

toml
[[fonts]]
family = "Charter"
src = "fonts/charter-regular.woff2"
weight = 400

[[fonts]]
family = "Charter"
src = "fonts/charter-italic.woff2"
weight = 400
style = "italic"

src may also be a list of files. unicode-range limits a face to some characters. Check that a font's license allows redistribution before you bundle it.

[syntax] ​

Code colours come from a Shiki theme:

toml
[syntax]
light = "solarized-light"
dark = "solar-dark.json"

A value is a Shiki theme name or a TextMate theme JSON in the theme folder. syntax = "nord" uses one theme for both appearances. Without [syntax], code uses GitHub Light and GitHub Dark.

Theme CSS ​

For decoration theme.toml cannot express, name a stylesheet with css = "theme.css". It is applied after the theme, so plain selectors win without !important.

css
/* Nested ordered lists count a., b., c. */
.ty-document .md-list-ol .md-list-ol > .md-list-item::after {
  content: counter(list-item, lower-alpha) ".";
}

Rules for theme CSS:

  • Decorate, don't re-layout. Colours, shadows, backgrounds and generated text are fine. Changes to sizes, margins and padding should go in theme.toml — Typora reads those to lay out long documents, and CSS changes to them can make scrolling jump.
  • Select by class. Blocks carry stable classes: .md-p, .md-heading with .md-h1….md-h6, .md-blockquote, .md-list-ul / .md-list-ol / .md-list-item, .md-fences, .md-table, .md-hr, .md-image, .md-math-block, .md-alert, .md-toc, inside .ty-document. Inline formatting uses plain tags (strong, em, code, a, mark, del). A paragraph that becomes a heading keeps its tag while you type, so style .md-h2, not h2.
  • No sibling or position selectors across blocks. Long documents only keep the visible blocks on the page, so h2 + p, :nth-child(), :first-child between blocks and CSS counters over headings give wrong results.
  • Leave the editing controls alone. The caret, selection, drag handles, toolbars and table handles are not part of the theme contract; recolour them with selection, caret and [chrome] instead.
  • Stay inside the folder. @import and url()s that leave the theme folder (including http: and https:) are dropped. data: URLs are allowed.
  • Light and dark: @media (prefers-color-scheme: dark) works, and <html> has data-appearance="light" or "dark".
  • Theme CSS shows in the editor, HTML and PDF export, but not in Word export.

Variables are available in CSS as custom properties: var(--theme-<name>) for anything in [vars], and the tokens Typora's own stylesheet uses (--color-bg, --color-text, --color-accent, --ty-body-font, …).

Converting a Typora 1 theme ​

A Typora 1 theme is a .css file, one per variant, written against Typora 1's page. Typora doesn't read these files. Convert each one into a theme folder with color-scheme set to light or dark, and move each rule to the first place that can hold it: [vars], [elements], [syntax], [chrome], then theme.css. A rule with no place stays behind.

Variables ​

The theme's own variables (--ice-accent) move to [vars] under the same name, whatever their type, and are used as $ice-accent and var(--theme-ice-accent).

Typora 1Typora
--bg-colorbg
--text-colortext
--primary-coloraccent
--monospacemono-font
--select-text-bg-colorselection
--highlight-colorhighlight
--meta-content-colormuted
--md-char-color, --heading-char-colorsyntax-marker
--code-block-bg-colorcode-bg
--code-block-text-colorelements.code-block.color
--side-bar-bg-colorchrome.sidebar-bg
--control-text-colorchrome.text
--active-file-bg-colorchrome.tree-selected-bg
--active-file-text-colorchrome.tree-selected-fg
--item-hover-bg-colorchrome.hover-bg
--window-borderchrome.border
.md-alert-text-* --alert-theme-coloralert-note, alert-tip, …

The page ​

  • #write font, size, line height and colour go to [vars], and caret-color to caret.
  • #write max-width includes its padding; content-width doesn't. Subtract the horizontal padding. Typora draws its own padding, so #write padding stays behind.
  • When #write has its own background (a card on a coloured page), that background is bg, not --bg-color, so HTML and PDF export get the paper. The colour behind it, usually on body, goes to chrome.window-bg, and the card's corners and shadow to surface-radius, surface-shadow and surface-border.

Elements ​

Headings, quotes, code blocks, tables, links and lists go to [elements]. Only the keys listed there exist: heading size and margin, code font-size, indent (the left margin of a quote or code block) and paragraph-spacing are the sizes a theme can set. Other sizes, margins and paddings stay behind, because moving them to CSS breaks long documents (Theme CSS). tr:nth-child(even) becomes elements.table.stripe. Gradients are not colours here: bar-color and header-bg take one colour.

Code ​

.cm-s-inner .cm-* colours become [syntax], chosen by the code block's own background: a light theme with dark code blocks needs a dark syntax theme under light. To keep a palette exactly, write a TextMate theme JSON. .cm-s-typora-default is source mode, and .CodeMirror-*, gutters and line numbers belong to the editor, so they stay behind.

Selectors in theme.css ​

Decoration that remains moves to theme.css with these selectors. Use box-shadow for a border that would add height.

Typora 1Typora
#write.ty-document
#write h1 … h6.md-h1 … .md-h6
p.md-p
blockquote.md-blockquote
pre.md-fences.md-fences
table.md-table (th, td and tr keep their tags inside it)
ul, ol, li.md-list-ul, .md-list-ol, .md-list-item
li::before, li::marker, custom counters.md-list-item::after, which draws the bullet or number. Nest for levels: .md-list-ul .md-list-ul > .md-list-item::after. ::before is taken.
li.task-list-item, li.md-task-list-item.md-task-list-item
.task-list-done.md-task-list-item:has(> .md-task-list-control:checked)
hr.md-hr
.md-toc-content, a.md-toc-innerThe same
.md-footnote, .md-def-name, .md-meta-blockThe same

Inline tags (strong, em, code, a, mark, del) stay as they are.

What stays behind ​

  • Anything outside #write that [chrome] can't hold: Typora 1's menus, dialogs, preferences, search panel, status bar and scrollbars.
  • @media print. Export uses the theme.
  • @import and remote url()s, so web fonts: name a system font stack instead, or bundle the font.
  • Selectors that depend on neighbouring blocks: h2 + p, :first-child, :nth-child() between blocks and counters over headings. Inside a table they are fine.
  • Per-level table of contents styles (.md-toc-h1 …) and .md-search-hit, which have no counterpart.

Packaging ​

A .typora-theme file is the theme folder zipped, either the folder itself or its contents:

sh
cd Themes && zip -r solar.typora-theme solar

Opening it in Typora asks to install it into the Themes folder under the folder's name.

In an extension ​

An extension can ship one or more themes. List their folders in the manifest:

json
{
  "id": "someone.solar",
  "name": "Solar",
  "version": "1.0.0",
  "tags": ["Theme"],
  "contributes": {
    "themes": ["themes/solar", "themes/solar-night"]
  }
}

Each folder holds a theme.toml as above. The folder's name (solar) is the theme's id inside the extension, so it must use a-z, 0-9 and -. Themes need no permissions.

How a theme reaches each output ​

OutputUses
EditorEverything
HTML, PDF, image exportEverything, with fonts and images embedded in the file
Word export[vars] and [elements] as Word styles (fonts, sizes, colours, spacing, alignment, upper-case, heading rules, quote bars or shading, code shading, table borders and header shading)
Extension pages and diagramsThe page colours and fonts (--typora-background, --typora-foreground, … in the View SDK)

Theme adapted from Nimbus (MIT).