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-boxand a default body padding. Addclass="ap-bleed"to<body>(or<html>) to set that padding to 0.
One-line start
Section titled “One-line start”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>Themes
Section titled “Themes”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 |
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-paperis a crisp white page with grey panels and blue links, in the style of a code host.polar-nightis a cool blue-grey dark theme; its primary buttons are frost blue with a dark label.printis 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.
What gets styled
Section titled “What gets styled”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,kbdandprein a monospace font on a tinted background;prescrolls horizontally instead of overflowing.button,input,selectandtextarea: 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-visiblering 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 |
Overriding with your own CSS
Section titled “Overriding with your own CSS”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.
Pinning and updates
Section titled “Pinning and updates”- At publish,
firnis resolved to the newest current snapshot,firn@1, and stored with that version. You can also writefirn@1yourself. The pin is returned astheme_refbypublish_artifactandupdate_artifact, and asartifact.theme_refbylist_artifacts; it isnullfor a version without a marker. - A theme snapshot never changes. A version published with
firn@1looks 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-lattenow resolves tocatppuccin-latte@2, butcontent="catppuccin-latte@1"still publishes with the@1look.get_authoring_guidelists every pinnable snapshot of a theme inversions.
| 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 pinnedfirn@1staysfirn@1, and a barefirnresolves 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 |
Errors
Section titled “Errors”- These make the publish or update fail with
invalid_input: more than one marker, a marker outside<head>, an emptycontent, a bad version (firn@0,firn@x, or a version that does not exist such asfirn@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.
Stored bytes and fonts
Section titled “Stored bytes and fonts”- Your HTML is stored exactly as you sent it, and
content_sha256is 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 adata:URL@font-faceand 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.
Accessibility and print
Section titled “Accessibility and print”- 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
@1snapshots 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), forhttp(s)links only. This applies tocode-paper,polar-night,printand the@2snapshots; the earlier@1snapshots are frozen without it.