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 h1p 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.
The engine injects page geometry as CSS variables. Never hardcode 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

CSS variable Impl

# 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.

Minimal tokens.css
: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

CSS variable Impl

# base/page-contract.css

The fixed-layout floor. Defines @page rules, page surfaces, and how content sits inside the printable area. Reads geometry from the engine-injected variables.

A typical page contract
@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;
}
CSS variable Impl

# base/typography.css

Default type scale inside MdxArea. Style headings, paragraphs, lists, blockquotes here — anything an MDX file might render.

CSS variable Impl

# base/print.css

@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.

A cover surface
/* 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.
Adding a pattern is content-driven. Don't pre-write utility classes "in case someone needs them" — wait until MDX actually calls for them, then add the file and document it in 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

  1. Start from a starter closest to your output (long-form → editorial-monograph; social cards → external creative skill).
  2. Replace tokens in tokens.css — colors, fonts, type scale. Most visual identity changes happen here alone.
  3. If your page geometry differs from the preset, set the <Press page> JSX prop in press/index.tsx — don't hardcode geometry in CSS.
  4. Adjust base/typography.css for type scale and rhythm if needed.
  5. Touch page-surfaces/*.css only when a specific Frame role needs custom layout.
  6. Add patterns/*.css entries when MDX actually uses a utility class — not before.
  7. Verify in the workbench (npm run dev) before npm run build.