Creating Themes
Baan’s theme system is fully open — every built-in theme is a folder of React components and CSS variables that you can inspect, fork, and modify. New themes can be created in three ways: describing what you want in the admin panel, using Claude Code with the /create-theme skill, or building one from scratch following the theme specification.
Every Baan theme also ships with its design prompts (see Theme Provenance). This is a core part of the theme specification — unlike traditional theme systems that distribute only the compiled code, Baan themes carry the intent and AI agent context that produced them.
Method 1: AI Theme Generation (Admin Panel) Beta
Section titled “Method 1: AI Theme Generation (Admin Panel) Beta”- Go to Admin → Settings → Theme
- Click Create new theme
- The panel opens with AI Agent (recommended) and Admin AI (beta) tabs
- To use Admin AI: switch to the Admin AI tab, enter a theme name and prompt, select a model, and click Generate
- The new theme appears in the theme list — select it and preview with the Customizer
Example Prompts
Section titled “Example Prompts”- “Minimalist Japanese design, lots of whitespace, serif headings, muted gray palette”
- “Bold magazine layout, full-bleed header images, high contrast, modern sans-serif”
- “Dark mode only, neon accent colors, cyberpunk aesthetic, monospace body text”
- “Clean academic journal style, Chicago citation format look, classic typography”
Method 2: AI Coding Agent
Section titled “Method 2: AI Coding Agent”The recommended approach for high-quality themes. Use Claude Code, Cursor, or Codex to instruct an AI agent to create the theme directly in your codebase. The agent reads Baan’s theme docs and generates a complete, working theme.
Claude Code includes a built-in /create-theme skill that runs a structured 5-step workflow. Cursor and Codex work with a natural language prompt — the Admin UI provides a copyable template on the AI Agent tab.
Claude Code: /create-theme Skill
Section titled “Claude Code: /create-theme Skill”Open Baan’s repository in Claude Code and type:
/create-themeThen describe what you want in the same message or when prompted:
Create a dark editorial theme inspired by longform magazines like The Atlantic.Serif headings (Playfair Display), wide article body (75ch), minimal sidebar,dark navy background, off-white body text, no rounded corners anywhere.What Happens Internally
Section titled “What Happens Internally”The skill follows a 5-step workflow — it doesn’t generate anything until step 4:
Step 1 — Research existing themes
Claude Code reads all files under themes/ to understand what already exists: naming conventions, CSS variable patterns, which component props are used, and where the built-in themes differ from each other.
Step 2 — Differentiation check Claude Code compares your prompt to existing themes and flags if something too similar already exists. If the new theme would be redundant, it asks how to distinguish it.
Step 3 — Design proposal (requires your approval) Claude Code presents a concrete design concept before writing any code. This includes:
- Theme name and folder name
- Color palette (background, text, accent, muted)
- Typography decisions (font families, scale, line height)
- Layout structure (sidebar yes/no, nav position, content width)
- Specific component behaviors (e.g., “post cards show only title and date, no excerpt”)
You approve, reject, or redirect before any files are created. This is the most important step — changes to the proposal cost nothing; changes to generated files cost time.
Step 4 — Implementation After approval, Claude Code generates:
themes/your-theme/├── theme.json # Theme metadata (name, display name, version, ai field)├── config.ts # Font imports, theme constants, exported config├── prompts/ # Theme Provenance (required)│ ├── concept.md # Design concept & principles (written from the approved proposal)│ └── generation.md # Optional: the actual prompt text used for generation├── styles/│ └── theme.css # All CSS custom properties└── components/ ├── layout/ │ └── Layout.tsx # Root layout: header + main content area + footer └── post/ ├── PostCard.tsx # Article card used on listing pages └── PostContent.tsx # Full article page (body, sidebar, metadata)theme.json includes an ai field recording the generation context (see Theme Provenance). prompts/concept.md captures the design intent in English so the theme can later be forked, understood by other AI agents, or regenerated from the prompt alone.
Step 5 — Registration
The theme auto-registers on the next npm run dev. The generate-theme-manifest script discovers all folders under themes/ that contain a valid theme.json and adds them to the manifest. No manual registration needed.
Writing Effective Prompts
Section titled “Writing Effective Prompts”The quality of the output depends heavily on the specificity of the prompt. Here’s what to include:
Layout structure — where is the sidebar, how wide is the content, does the header scroll or stick?
Left sidebar with tree navigation (like Obsidian Publish), fixed on scroll.Main content area 70ch wide, no right sidebar.Typography — font choices, size relationships, weights
Headings: Playfair Display, 700 weight, tight letter-spacing.Body: 18px Georgia, 1.8 line height.Code: JetBrains Mono.Color system — dark/light, specific values if you have them, accent color usage
Dark theme only. Background #0d1117, text #e6edf3 (GitHub Dark palette).Single accent: #58a6ff. No warm colors.Feel / reference sites — named references work well
Inspired by The Verge (2023): large type, aggressive color blocks,full-bleed images, no borders between content areas.Explicit constraints — what you don’t want
No rounded corners anywhere.No drop shadows.No hover animations.Post cards: title only, no excerpt, no cover image.Fine-Tuning After Generation
Section titled “Fine-Tuning After Generation”Generated themes are a starting point, not a final product. The most efficient fine-tuning workflow:
1. CSS variables first
Most visual changes can be made entirely in styles/theme.css without touching any component files. Start there:
:root { --color-bg: #0d1117; --color-text: #e6edf3; --color-accent: #58a6ff; --color-muted: #8b949e; --font-heading: 'Playfair Display', Georgia, serif; --font-body: Georgia, 'Times New Roman', serif; --font-mono: 'JetBrains Mono', 'Fira Code', monospace; --spacing-content: 70ch; --spacing-sidebar: 260px; --radius: 0; /* No rounded corners */}2. Ask Claude Code to make specific changes
After reviewing the generated theme in the browser, describe what needs to change:
In the generated theme, the post card is too tall — remove the excerpt text,keep only the title, date, and category tag.The header is sticky but I want it to scroll away.Remove position: sticky and the backdrop-filter.Increase the body font size from 16px to 18px and set line-height to 1.85.3. Fork if you want a variant
If you want the same theme with a different color palette (e.g., a light version of a dark theme), ask Claude Code to fork it:
Fork the `atlantic-dark` theme into `atlantic-light`.Swap the background and text values, keep all layout and typography unchanged.4. Preview while editing
With npm run dev running, theme file changes apply on save. Use Admin → Customizer to preview the theme on your actual content across desktop, tablet, and mobile breakpoints.
Method 3: Manual Theme Creation
Section titled “Method 3: Manual Theme Creation”For full control, create a theme folder directly in themes/[theme-name]/.
Required Files
Section titled “Required Files”themes/my-theme/├── theme.json # Theme metadata (includes `ai` field)├── config.ts # Font imports, exported theme config├── prompts/ # Theme Provenance (required)│ └── concept.md # Design concept & principles (English)├── styles/│ └── theme.css # CSS custom properties└── components/ ├── layout/ │ └── Layout.tsx # Root layout wrapper └── post/ ├── PostCard.tsx # Article card for listing pages └── PostContent.tsx # Full article page layouttheme.json
Section titled “theme.json”{ "name": "my-theme", "displayName": "My Theme", "description": "A clean, minimal theme with serif headings.", "author": "Your Name", "version": "1.0.0", "ai": { "agent": "Manual", "skills": [], "generatedAt": "2026-04-18" }}The ai field records the theme’s provenance. For hand-built themes, set agent to "Manual" and leave skills as an empty array.
CSS Variables
Section titled “CSS Variables”Themes use CSS custom properties for all design tokens:
:root { --color-bg: #ffffff; --color-text: #111111; --color-accent: #0070f3; --color-muted: #6b7280; --font-heading: 'Georgia', serif; --font-body: 'Inter', sans-serif; --font-mono: 'JetBrains Mono', monospace; --spacing-content: 45rem; --radius: 0.375rem;}Components
Section titled “Components”Each component receives props from Baan’s data layer. Components are React/TSX files that can use Tailwind CSS or the CSS variables defined in styles/theme.css.
For the full component API and prop types, see the AI Agent Built Themes Guide in the main repository.
Theme Architecture (For Engineers)
Section titled “Theme Architecture (For Engineers)”Registration Pipeline
Section titled “Registration Pipeline”Themes are discovered and registered at build time, not runtime:
npm run dev / npm run build └── generate-theme-manifest (prebuild script) └── scans themes/*/theme.json └── validates required files exist └── writes lib/generated/theme-manifest.json └── writes lib/generated/theme-components.ts └── writes lib/generated/theme-fonts.ts └── imported by the theme loader at runtimeThe generated manifest lists all available theme IDs with their metadata. The active theme ID is stored in the settings table (key: theme). On each request, Baan’s rendering layer reads the manifest and uses the generated static component loaders to resolve the active theme.
Runtime Rendering Flow
Section titled “Runtime Rendering Flow”Request → Next.js App Router └── reads active theme ID from settings (cached) └── loads themes/[active]/components/layout/Layout.tsx └── Layout renders: ├── Header (from theme or Baan default) ├── <slot /> — page-specific content │ ├── Listing page: PostCard[] from theme │ └── Post page: PostContent from theme └── Footer (from theme or Baan default)Each page type passes a typed props object to the theme components. The theme components are responsible only for rendering — all data fetching happens in Baan’s page layer before the theme components are called.
CSS Variable Cascade
Section titled “CSS Variable Cascade”Themes define variables on :root. Baan’s global stylesheet defines fallback values for every variable so that a partially specified theme still renders correctly:
Baan global defaults (lowest priority) └── theme :root variables (override) └── component-scoped overrides (highest priority)This means you can create a minimal theme that only overrides a few variables and inherits the rest from Baan’s defaults. The built-in geek theme, for example, primarily overrides font and spacing variables while inheriting the color system from default.
Component Isolation
Section titled “Component Isolation”Theme components run inside a React error boundary. If a theme component throws an error (e.g., accessing a prop that doesn’t exist in a particular context), Baan renders a fallback UI rather than crashing the page. This means a broken theme never takes down the entire site.
Theme components are loaded with next/dynamic (lazy loading) so they don’t affect the bundle size of other themes.
Adding a Header or Footer Override
Section titled “Adding a Header or Footer Override”Layout.tsx can import and render custom Header and Footer components from within the theme folder. If a theme doesn’t define its own header or footer, Baan’s default nav renders automatically.
To add a theme-specific header:
import Header from './Header'import Footer from './Footer'
export default function Layout({ children, site }) { return ( <> <Header site={site} /> <main>{children}</main> <Footer site={site} /> </> )}CSS Variable Reference
Section titled “CSS Variable Reference”| Variable | Default | Purpose |
|---|---|---|
--color-bg | #ffffff | Page background |
--color-text | #111111 | Body text |
--color-accent | #0070f3 | Links, buttons, highlights |
--color-muted | #6b7280 | Secondary text, dates, tags |
--color-border | #e5e7eb | Dividers, card borders |
--font-heading | system-ui, sans-serif | Heading font stack |
--font-body | system-ui, sans-serif | Body text font stack |
--font-mono | monospace | Code blocks |
--spacing-content | 45rem | Max width of article body |
--spacing-sidebar | 280px | Sidebar width |
--radius | 0.375rem | Border radius for cards/buttons |
Activating a Custom Theme
Section titled “Activating a Custom Theme”After placing the theme folder in themes/, restart the dev server (or run npm run build in production) to register the new theme. It will then appear in Admin → Customizer and can be activated from there.
Theme Provenance
Section titled “Theme Provenance”Baan specifies Theme Provenance: every theme ships with the prompts and AI-agent context that produced it, not just the compiled code. This is a core part of Baan’s theme specification and sets it apart from traditional theme systems like WordPress, Hugo, or Astro.
Required structure
Section titled “Required structure”themes/<name>/├── theme.json # includes "ai" field└── prompts/ # required ├── concept.md # required — design concept & principles (English) ├── generation.md # optional — the actual generation prompt └── skills/ # optional — custom Claude Code skill files bundled with the theme └── <name>.mdtheme.json ai field
Section titled “theme.json ai field”{ "ai": { "agent": "Claude Code", "skills": ["create-theme"], "generatedAt": "2026-04-18" }}| Field | Purpose |
|---|---|
agent | Which AI agent produced the theme: "Claude Code", "Cursor", "Codex", "Baan Admin", "Manual", etc. |
skills | Optional array — built-in skill names (e.g. "create-theme") or references to files in prompts/skills/. Both forms can coexist in the same array. |
generatedAt | Optional ISO date. |
The ai field is optional at the schema level — missing ai is treated as “unknown provenance” (no error). The theme-manifest generator does not read prompts/; build output is unaffected by provenance data.
prompts/concept.md template
Section titled “prompts/concept.md template”# <Theme Name> — Theme Concept
## Overview<1-2 sentence positioning statement>
## Design Principles- ...
## Target Users<who this theme is for>
## Layout<high-level layout description>
## Typography<font choice + rationale>
## Color Palette<light/dark approach, key CSS variables>
## Component Notes<any theme-specific component behavior worth documenting>Why Theme Provenance
Section titled “Why Theme Provenance”- Fork through the prompt, not the code — edit
concept.mdand hand it to Claude Code or Cursor to regenerate a variation, even as a non-engineer. - Carry intent across AI agents — the same concept document can be fed to any AI agent (results will drift — that’s expected; it’s a recipe, not a reproducibility guarantee).
- Credit the author’s design decisions — the rationale lives inside the theme folder, not just in a separate README that can get lost.
Bundling custom skills with a theme
Section titled “Bundling custom skills with a theme”If a theme was generated using a custom Claude Code skill (one you wrote yourself, not Baan’s built-in create-theme), bundle the skill file with the theme:
themes/my-theme/prompts/skills/my-custom-generator.mdAdd the skill name to theme.json:
{ "ai": { "agent": "Claude Code", "skills": ["create-theme", "my-custom-generator"], "generatedAt": "2026-04-18" }}Consumers who install the theme can copy the bundled skills into their own .claude/skills/ directory using the import-theme-skills skill, which is shipped with Baan.
Backward compatibility
Section titled “Backward compatibility”Themes without prompts/ still load — the system issues a warning only. External themes written before this specification existed continue to work. New themes should include prompts/concept.md going forward.