Themes
A theme is the brand: colours, typography, logo, font faces and free-form brand details. Templates reference tokens, so one saved template renders under any theme.
import type { Theme } from "@broadpaper/core";
export const meridian: Theme = {
id: "meridian",
name: "Meridian Wealth",
logo: "/brands/meridian.svg",
logoDark: "/brands/meridian-white.svg",
typography: { headingFont: "Inter, system-ui, sans-serif", bodyFont: "Inter, system-ui, sans-serif", baseFontSize: 11, headingWeight: 700 },
colors: {
primary: "#1F3A5F", secondary: "#4A6FA5", accent: "#E0B04B",
text: "#1A1D21", mutedText: "#6B7280", surface: "#FFFFFF", background: "#F5F6F8", border: "#E2E5EA",
success: "#1E8E5A", warning: "#C77D0A", danger: "#C0392B",
chart: ["#1F3A5F", "#4A6FA5", "#E0B04B", "#7FB3D5"]
},
fonts: [
{ family: "Inter", src: ["https://cdn.example.com/fonts/Inter-Regular.woff2", "https://cdn.example.com/fonts/Inter-Regular.ttf"], weight: 400 },
{ family: "Inter", src: ["https://cdn.example.com/fonts/Inter-Bold.woff2", "https://cdn.example.com/fonts/Inter-Bold.ttf"], weight: 700 }
],
radius: 8,
table: { headerBackground: "#EEF2F7", headerColor: "#1F3A5F", zebraBackground: "#FAFBFC", borderColor: "#E2E5EA" },
meta: { company: "Meridian Wealth Management Ltd", regulator: "Authorised and regulated by the FCA (FRN 123456)" }
};Tokens
| Token | Resolves to |
|---|---|
$colors.primary … $colors.<any> | Theme colours, including custom names you add. |
$typography.headingFont, $typography.bodyFont, $typography.monoFont | Font stacks. |
$table.headerBackground, $table.headerColor, $table.zebraBackground, $table.borderColor | Table defaults. |
$logo, $logoDark | Logo URLs. The Logo block picks the dark variant on dark backgrounds automatically. |
In expressions, theme.meta.company and friends expose brand details for headers, footers and disclaimers. Missing values fall back to the built-in default theme and are reported as missing-theme-value warnings.
The cascade
Theme → document text styles → block defaults → instance overrides. New templates start with document styles that reference tokens (h1.color = $colors.primary), so they re-brand cleanly. Designers can still choose a literal colour for a block; hosts can forbid that with features.styleOverrides = false.
Fonts
theme.fonts is the one place a font is declared. From that declaration BroadPaper:
- injects
@font-facerules for the canvas, waits for them before measuring, and re-measures if they arrive late; - offers them in the designer's font pickers — the theme's heading, body and mono fonts and these families, and nothing else, since a font the theme does not supply cannot be embedded — and counts them as available when validating;
- embeds them in the PDF — the browserless engine, the engine measuring the canvas and the render service all read the same list, as does the Chromium backend.
Include a TrueType (.ttf) or OpenType (.otf) file among each face's sources. A browser reads any format, but the browserless PDF engine reads only those two. List a WOFF2 first if you like — the browser takes it and the engine skips it for the .ttf beside it. A face with no TrueType or OpenType source is still drawn on screen, and the PDF draws that text in a standard face and says so in warnings.
A few rules follow from where the file is read:
- One file per weight.
weight: "400 700"on a variable font works on screen, but the engine is given the file at the first weight only. - Sources. A
data:URI works everywhere. Anhttps://address is fetched once per process and needs CORS in a browser, as@font-facedoes. Any other string is a file path in Node and an address relative to the page in a browser — so a theme used in both needs an absolute address or adata:URI. - The render service reads a theme that arrives in a request more carefully: web addresses only on its
allowedHosts, and no file paths. See the render service.
If your app loads fonts via <link> instead, the canvas still picks them up — the editor listens for font loading and re-lays out — but the PDF engine never sees a <link>, so declare them in the theme for the file to match.
Switching at runtime
The toolbar's theme menu lists themes; designer.setTheme(theme) switches programmatically. For rendering, pass any theme to renderPdf({ template, data, theme }).