Theming & brands
A brand is one typed token object (colors, fonts, a chart palette), and the whole deck (slides, code, plugins) derives its look from it. Nothing is hardcoded in slides: a slide uses text-primary, not text-blue-500, so it’s right under any brand.
Use a built-in brand
Section titled “Use a built-in brand”The theme styles ship a few brands (liebstoeckel, nocturne, acme, sunset). Import the styles once in your entry and pick one by name:
import "@liebstoeckel/theme/styles.css";import { Present } from "@liebstoeckel/engine";
<Present brands={["nocturne"]} slides={[…]} />Define your own brand
Section titled “Define your own brand”Author it with defineTheme and hand it to <Present> via brandThemes. The engine injects it as a [data-brand] stylesheet, so you ship a brand without touching the theme package:
import "@liebstoeckel/theme/styles.css";import { Present } from "@liebstoeckel/engine";import { defineTheme } from "@liebstoeckel/theme";
const acme = defineTheme({ name: "acme", colors: { bg: "#0b0e14", surface: "#141925", border: "#222a3a", text: "#e6eaf2", muted: "#8a93a6", primary: "#3b82f6", // brand accent: "#22d3ee", // accent accent2: "#f0abfc", // optional secondary accent onPrimary: "#ffffff", // text/icons on a primary fill }, fonts: { heading: '"Inter", system-ui, sans-serif', body: '"Inter", system-ui, sans-serif', mono: '"JetBrains Mono", ui-monospace, monospace', }, viz: ["#3b82f6", "#22d3ee", "#f0abfc", "#a3e635", "#fbbf24"], // optional chart palette glow: { a: "#10233f", b: "#0b2530" }, // optional atmosphere gradient});
<Present brands={["acme"]} brandThemes={[acme]} slides={[…]} />That’s it: brands names the active brand, brandThemes supplies its tokens. (Bring fonts via @font-face / a <link> in your index.html.)
Available tokens
Section titled “Available tokens”| CSS variable | Tailwind | Meaning | Required |
|---|---|---|---|
--brand-bg / --brand-surface | bg-bg / bg-surface | page + panel backgrounds | ✓ |
--brand-text / --brand-muted | text-text / text-muted | primary + secondary text | ✓ |
--brand-primary / --brand-accent | text-primary / text-accent | brand + accent | ✓ |
--brand-on-primary | text-on-primary | content on a primary fill | ✓ |
--brand-font-heading/body/mono | font-heading/body/mono | typefaces | ✓ |
--brand-border | border-border | hairlines (falls back to a tint of text) | optional |
--brand-accent2 | text-accent2 | secondary accent | optional |
--brand-viz-0…n | (none) | chart-series palette (theme.viz) | optional |
--brand-glow-a/b | (none) | atmosphere gradient stops (theme.glow) | optional |
How it works (the bridge)
Section titled “How it works (the bridge)”-
A brand is a
[data-brand="name"] { --brand-*: … }block. Built-ins live in the theme styles; yourbrandThemesare injected as the same kind of block. -
@theme inlinein the theme CSS maps Tailwind utilities onto those variables, sotext-primaryresolves tovar(--brand-primary)at the point of use, under whicheverdata-brandis active. -
The deck sets
document.body.dataset.brand, so the whole deck re-skins instantly, no rebuild.
Code and plugins inherit automatically: Shiki highlights against the brand tokens (see Animated code), and plugins style against var(--brand-*) (and read theme.viz for charts), so polls, Q&A, and reactions are on-brand for free.
Contributing a shared brand
Section titled “Contributing a shared brand”To make a brand available to every deck by name (instead of per-deck brandThemes), add it to the theme package: drop a defineTheme(...) file in packages/theme/src/brands/, export it from brands/index.ts, and run bun run gen to regenerate brands.generated.css.
Code-first presentations your agent can author. One file out, no server.
Comments