Skip to content

Themes

Built-in themes style a published page without your own stylesheet. They are optional: a page without the marker is served exactly as you sent it.

Full-bleed layouts: themed pages use box-sizing: border-box and a default body padding. Add class="ap-bleed" to <body> (or <html>) to set that padding to 0.

Put this in <head>:

<meta name="ap-theme" content="firn">

Plain HTML (headings, paragraphs, links, tables, code, forms) is now styled. Agents can call the read-only MCP tool get_authoring_guide for the same catalogue, class list, snippet and limits as this page (no CSS). Add ap-* classes when you want layout:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="ap-theme" content="firn">
<title>Q3 review</title>
</head>
<body>
<main class="ap-page ap-stack">
<h1>Q3 review</h1>
<p class="ap-lead">Revenue up, churn flat, one launch slipped.</p>
<div class="ap-grid">
<div class="ap-kpi"><span class="ap-kpi-value">€1.2M</span><span class="ap-kpi-label">Revenue</span></div>
<div class="ap-kpi"><span class="ap-kpi-value">2.1%</span><span class="ap-kpi-label">Churn</span></div>
</div>
<p class="ap-callout ap-warn">The billing launch moved to November.</p>
</main>
</body>
</html>
content Pinned as Family Mode Palette credit
firn firn@1 Firn Light; dark when the reader’s OS is in dark mode Artifact Publisher
light light@1 Firn Light Artifact Publisher
dark dark@1 Firn Dark Artifact Publisher
catppuccin-latte catppuccin-latte@2 Catppuccin Light Catppuccin Latte (MIT, github.com/catppuccin/palette)
catppuccin-frappe catppuccin-frappe@1 Catppuccin Dark Catppuccin Frappé (MIT, github.com/catppuccin/palette)
catppuccin-macchiato catppuccin-macchiato@1 Catppuccin Dark Catppuccin Macchiato (MIT, github.com/catppuccin/palette)
catppuccin-mocha catppuccin-mocha@1 Catppuccin Dark Catppuccin Mocha (MIT, github.com/catppuccin/palette)
tokyonight-night tokyonight-night@1 Tokyo Night Dark Tokyo Night Night (Apache-2.0, github.com/folke/tokyonight.nvim)
tokyonight-storm tokyonight-storm@1 Tokyo Night Dark Tokyo Night Storm (Apache-2.0, github.com/folke/tokyonight.nvim)
tokyonight-moon tokyonight-moon@1 Tokyo Night Dark Tokyo Night Moon (Apache-2.0, github.com/folke/tokyonight.nvim)
tokyonight-day tokyonight-day@2 Tokyo Night Light Tokyo Night Day (Apache-2.0, github.com/folke/tokyonight.nvim)
code-paper code-paper@1 Code Paper Light Primer primitives (MIT)
polar-night polar-night@2 Polar Night Dark Nord (MIT, github.com/nordtheme/nord)
print print@2 Print Light, even when the reader’s OS is dark Artifact Publisher

Only firn follows the reader’s OS setting; every other theme always uses the one mode shown. Every theme prints light. All themes share the same layout, classes and custom properties; only the colours differ.

  • code-paper is a crisp white page with grey panels and blue links, in the style of a code host.
  • polar-night is a cool blue-grey dark theme; its primary buttons are frost blue with a dark label.
  • print is made for paper and PDFs: black-ish text on white, and panels, code and badges marked by borders instead of tinted backgrounds. Its primary button is outlined rather than filled. It stays light on a dark OS.

polar-night@2 raises control borders from 3.01:1 to 4.50:1 against panels. print@2 outlines the primary button. Pages pinned to polar-night@1 or print@1 keep those exact bytes.

The Firn and Print palettes are Artifact Publisher’s own (Firn is the same as this site). The other themes use the upstream colours, mapped to the theme roles:

Role (--ap-*) Catppuccin Tokyo Night Code Paper (Primer) Polar Night (Nord)
bg base bg bgColor-default nord0
surface mantle bg_dark bgColor-muted nord1
border surface1 fg_gutter borderColor-default nord3
text text fg fgColor-default nord4
muted subtext1 fg_dark fgColor-muted nord3, lightened
accent blue blue fgColor-accent nord8
accent-ink base bg fgColor-onEmphasis nord0
success / warn / danger green / yellow / red green / yellow / red fgColor-success / -attention / -danger nord14 / nord13 / nord11 (lightened)
border-control overlay1 dark5 fgColor-disabled nord3, lightened
accent-hover accent mixed 16% towards text same same same

Where an upstream colour fails the contrast rules below, its lightness is adjusted just enough to pass, and the hue stays the same. This affects Catppuccin Latte (accent, border-control, success, warn), Tokyo Night Day (text, muted, accent, danger) and Polar Night (muted, border-control, danger). Code Paper and the dark Catppuccin and Tokyo Night flavours ship upstream colours unchanged.

Element defaults (“classless-lite”):

  • Page background and text colour, a system font stack, line height and heading sizes.
  • Links in the accent colour, always underlined.
  • Tables with row dividers, a tinted header row and tabular numbers.
  • code, kbd and pre in a monospace font on a tinted background; pre scrolls horizontally instead of overflowing.
  • button, input, select and textarea: inherited font, visible 3:1 borders, padding and radius. Checkboxes and radios use the text colour (accent-color; the accent is kept for links and the primary action); ranges and progress bars use the accent colour.
  • A visible :focus-visible ring in the text colour on everything focusable (the accent is kept for links and the primary action).
  • Disabled controls stay legible: muted text on a tinted background with a dashed border, not faded-out opacity. Disabled checkboxes and radios keep the browser’s own dimmed look.
  • Images, SVG, video and canvas never exceed their container width.

Classes:

Class Use
ap-page Centred page column (max 72rem)
ap-narrow Centred reading column (max 44rem)
ap-stack Vertical stack with a gap
ap-cluster Wrapping row with a gap: toolbars, tags, buttons
ap-split Row with items pushed to both ends
ap-grid Responsive grid; set --ap-min to change the column minimum (default 15rem)
ap-gap-1 … ap-gap-5 Gap for stack/cluster/split/grid: 0.25, 0.5, 1, 1.5, 2.5rem
ap-card Tinted panel with border and padding
ap-callout Highlighted note with a coloured edge
ap-badge Small pill label: a tone-coloured dot plus the word in the text colour (status is never colour-only)
ap-info, ap-success, ap-warn, ap-danger Tone for ap-badge and ap-callout; the default and ap-info are muted, never the accent
ap-bleed On <html> or <body>: no default body padding
ap-kpi, ap-kpi-value, ap-kpi-label Metric tile with a large number and a caption
ap-primary Filled accent button (other buttons are neutral)
ap-lead Larger, muted intro paragraph
ap-muted, ap-small, ap-mono Secondary colour, smaller text, monospace
ap-num Tabular, end-aligned numbers for table cells
ap-scroll Horizontal scroll wrapper, e.g. around a wide table
ap-sr-only Visually hidden but read by screen readers

The theme lives entirely in a cascade layer (@layer ap-theme), every selector has zero specificity (:where()), and nothing uses !important. Any ordinary CSS you write, in a <style> element or a style="" attribute, therefore wins over the theme, whatever its specificity or position. You only override what you want to change.

The colours are custom properties, so you can retune a theme without restating rules:

<style>
:root { --ap-accent: #8a3ffc; --ap-accent-hover: #6929c4; }
</style>

Available properties: --ap-bg, --ap-surface, --ap-border, --ap-text, --ap-muted, --ap-accent, --ap-accent-ink, --ap-accent-hover, --ap-success, --ap-warn, --ap-danger, --ap-border-control (form-control borders), --ap-font-sans, --ap-font-mono, --ap-radius, --ap-radius-sm, --ap-space-1 … --ap-space-5 and --ap-measure. If you change colours, checking contrast is up to you.

Your own @layer rules win too: the theme’s stylesheet comes first in the page, so ap-theme is the first, lowest-priority layer.

  • At publish, firn is resolved to the newest current snapshot, firn@1, and stored with that version. You can also write firn@1 yourself. The pin is returned as theme_ref by publish_artifact and update_artifact, and as artifact.theme_ref by list_artifacts; it is null for a version without a marker.
  • A theme snapshot never changes. A version published with firn@1 looks the same in a year; improvements ship as a new snapshot (firn@2) that only new versions pick up.
  • A bare name always takes the newest snapshot. Older snapshots stay valid as explicit pins, also for new versions: catppuccin-latte now resolves to catppuccin-latte@2, but content="catppuccin-latte@1" still publishes with the @1 look. get_authoring_guide lists every pinnable snapshot of a theme in versions.
Theme Snapshots What changed
catppuccin-latte @1, @2 (newest) @2: darker accent (links, primary button) for 4.6:1; printed link URLs
tokyonight-day @1, @2 (newest) @2: slightly darker text, muted and accent for 4.6:1, link hover in cards included; printed link URLs
Existing themes other than those above @1 Unchanged
code-paper, polar-night, print @1 New palettes; printed link URLs
  • On update_artifact, each new version is resolved from its own HTML: no marker means no theme, a pinned firn@1 stays firn@1, and a bare firn resolves to whatever is current at that moment. Older versions keep their own pin.
The new version’s HTML has Its theme_ref
No marker null (the previous version’s theme is not carried over)
A pin, e.g. content="dark@1" dark@1, kept as written
A bare name, e.g. content="firn" The newest current snapshot of firn at the time of the update
  • These make the publish or update fail with invalid_input: more than one marker, a marker outside <head>, an empty content, a bad version (firn@0, firn@x, or a version that does not exist such as firn@2), an unknown or retired theme, and a marker whose quote is never closed (content="firn>). The message lists the valid themes with their versions and shows the correct snippet. Markers inside comments, <script>, <style> or <template> are ignored.
  • A retired snapshot keeps serving the versions already pinned to it but cannot be used for new versions.
  • Your HTML is stored exactly as you sent it, and content_sha256 is the hash of those bytes. The theme stylesheet is added only to the page response, as an inline <style data-ap-theme="firn@1"> right after <head>, before your own CSS.
  • Nothing is fetched. The theme is a single inline stylesheet with no URLs, imports or web fonts. Pages can’t load outside fonts or scripts.
  • Fonts are system stacks (system-ui, ui-monospace, …). If you want a specific typeface, embed it yourself as a data: URL @font-face and set --ap-font-sans.
  • The theme adds about 8 KB (about 2.4 KB compressed) to each themed page response. It does not count towards your storage or the 10 MiB limit.
  • Keep your own CSS small. If a themed page has more than 3 KiB (3072 bytes) of <style> content, publish and update return a warning that suggests relying on the theme. It is never an error, and pages without a theme never get it.
  • Text colours reach at least 4.6:1, a small margin over WCAG 2.1 AA’s 4.5:1, on both backgrounds the theme paints (page and panel), in light, dark and print: body and muted text, links and link hover, code, button labels, placeholders, disabled labels, badges and callouts. Form-control borders (disabled ones too), focus rings, checked controls, badge dots, callout edges and the primary button edge reach 3:1 on both. An automated test checks this for every theme, mode and control state. The two @1 snapshots that predate the 4.6 target (catppuccin-latte@1, tokyonight-day@1) retain their original tested 4.5:1 guarantee and stay available as pins; their old matrix did not include link hover on panels.
  • Links are always underlined, focus is always visible, and the theme adds no motion.
  • Printing (or saving as PDF) uses a white page with dark text for every theme, including dark. Table header rows repeat on each page, and cards, callouts, figures, code blocks and table rows avoid splitting across pages.
  • On paper, links print their address after the text, e.g. the report (https://example.com/q3), for http(s) links only. This applies to code-paper, polar-night, print and the @2 snapshots; the earlier @1 snapshots are frozen without it.