press/theme/
Themes
OpenPress reads CSS from a fixed-layout theme directory — tokens.css, fonts.css, base/, page-surfaces/, shell/, and optional patterns/. Each folder has a single role; nothing else is loaded.
A theme is just CSS files in a known layout. The engine resolves them at build time, injects
page-geometry variables, and ships everything as flat theme.css bundled output.
There's no theme runtime, no JS hooks — adding a new theme means writing CSS that consumes the
documented tokens.
Directory contract
What the engine reads from press/theme/
| Name | Type | Default | Description |
|---|---|---|---|
tokens.css | required | CSS variables — colors, fonts, spacing, page geometry fallback. The first file other CSS depends on. | |
fonts.css | required | @font-face rules for bundled webfonts. May be empty if you only use system fonts. | |
base/page-contract.css | required | @page rules + page-surface CSS that consumes the geometry tokens. Defines the printable area. | |
base/typography.css | required | Default type scale for h1 … p inside MdxArea. | |
base/print.css | required | @media print rules for PDF export. May be minimal but the file must exist. | |
page-surfaces/{cover,toc,back-cover}.css | optional | Per-Frame-role styling. Empty stubs are kept so a starter can add a cover later without touching the base layout. | |
shell/reader-controls.css | optional | Workbench / reader chrome overrides. Most starters leave this empty since the framework supplies the default controls. | |
patterns/*.css | optional | Content-opt-in utility classes — figure grids, chart frames, table cell helpers. Long-form A4 starters ship a small set; slide / social starters skip the folder. |
210mm or 1080px in your theme — read from
--openpress-page-width, --openpress-page-height,
--openpress-page-aspect-ratio. Geometry comes from config.page
(Workspace config → Page geometry).
tokens.css
The single source of truth for visual style. Every other theme file reads from these tokens — never hardcode color / font / spacing values elsewhere.
:root {
/* Color */
--op-ink: #1a1a1a;
--op-ink-strong: #000;
--op-paper: #fff;
--op-paper-soft: #fafafa;
--op-accent: #2563eb;
--op-hairline: #e5e5e5;
/* Type */
--op-font-body: "Inter", system-ui, sans-serif;
--op-font-display: "Inter Display", "Inter", sans-serif;
--op-font-mono: "JetBrains Mono", ui-monospace, monospace;
/* Type scale */
--op-text-xs: 0.72rem;
--op-text-sm: 0.85rem;
--op-text-base: 1rem;
--op-text-lg: 1.15rem;
--op-text-2xl: 1.6rem;
/* Spacing */
--op-space-1: 4px;
--op-space-2: 8px;
--op-space-3: 12px;
--op-space-4: 16px;
/* Page geometry — these have engine-injected fallbacks; you can
override here only when you need a non-config default. */
--openpress-page-width: 210mm;
--openpress-page-height: 297mm;
}
Token names use the --op- prefix by convention; the page geometry trio uses
--openpress-page-* because it's injected by the engine. Custom themes can add
their own variables — anything not starting with --openpress- belongs to your
theme.
base/ — the layout floor
The fixed-layout floor. Defines @page rules, page surfaces, and how content sits inside the printable area. Reads geometry from the engine-injected variables.
@page {
size: var(--openpress-page-width) var(--openpress-page-height);
margin: 0;
}
.openpress-page {
width: var(--openpress-page-width);
height: var(--openpress-page-height);
background: var(--op-paper);
color: var(--op-ink);
/* Inner padding lives here — pages are flush to the @page edge,
content insets via this padding. */
padding: 22mm 18mm;
} Default type scale inside MdxArea. Style headings, paragraphs, lists, blockquotes here — anything an MDX file might render.
@media print rules. Page breaks, color profile, font hinting, anything that differs between screen and PDF. May be minimal but the file must exist.
page-surfaces/ — per-role styling
Each file corresponds to a Frame role="…" namespace. Names map straight to the
file: a Frame with role="document.cover" reads cover.css,
role="document.toc" reads toc.css, and so on. Files are optional but
starter skills often ship empty stubs so adding a cover later doesn't require touching the
base file layout.
/* page-surfaces/cover.css */
.openpress-page[data-role="document.cover"] {
display: grid;
place-content: end start;
padding: 28mm 22mm;
background: linear-gradient(180deg, var(--op-paper) 0%, var(--op-paper-soft) 100%);
}
.openpress-page[data-role="document.cover"] h1 {
font-family: var(--op-font-display);
font-size: 64pt;
line-height: 1.05;
} patterns/ — opt-in utility classes
The only folder whose presence depends on content typology. A4 long-form starters ship a small utility library (figure grids, chart frame wrappers, table cell helpers); slide and social starters render one main block per page and don't need it, so they skip the folder entirely.
Common pattern files (editorial-monograph / academic-paper)
| Name | Type | Default | Description |
|---|---|---|---|
figure-grid.css | utility | Multi-column figure layouts (.figure-grid, .figure-grid--2, etc). | |
_chart-frame.css | utility | Outer wrapper for <ChartFigure> — caption placement, footnote rules. | |
table-utilities.css | utility | Cell helpers — .cell-numeric, .cell-strong, alternating-row hooks. |
patterns/README.md. The bundled starter skills do this; follow that habit.
shell/ — reader chrome overrides
shell/reader-controls.css overrides the framework's default workbench chrome
(toolbar buttons, page-zoom controls, panel borders). Most themes leave this empty since the
defaults work fine; override only when your brand needs different controls.
Authoring a new theme
- Start from a starter closest to your output (long-form → editorial-monograph; social cards → external creative skill).
- Replace tokens in
tokens.css— colors, fonts, type scale. Most visual identity changes happen here alone. - If your page geometry differs from the preset, set the
<Press page>JSX prop inpress/index.tsx— don't hardcode geometry in CSS. - Adjust
base/typography.cssfor type scale and rhythm if needed. - Touch
page-surfaces/*.cssonly when a specific Frame role needs custom layout. - Add
patterns/*.cssentries when MDX actually uses a utility class — not before. - Verify in the workbench (
npm run dev) beforenpm run build.