Create books using HTML, CSS, and PrinceXML
Crown is a modern development framework that brings the best of web development to PDF book creation. Write your content in Markdown, style it with CSS, and generate print-quality PDFs with hot reload and instant preview.
- Markdown content with YAML frontmatter, ordered however you like
- Handlebars templates with partials and custom helpers
- Hot reload — save a file, watch the PDF update in the browser
- PrinceXML for the typesetting web browsers can't do
- Type-safe config with autocomplete via
defineConfig - Data-driven pages from CSV, JSON or YAML
- Imposition — arrange finished pages onto press sheets for folding and stapling
# Create a new book project
npx crown create my-book
# Start development
cd my-book
npm install
npm run devYour browser will open with a live preview of your PDF. Edit any file and watch it update instantly!
# As a project dependency
npm install @romello/crown
# Or use directly with npx
npx @romello/crown create my-bookRequirements:
- Node.js 18 or higher
- PrinceXML installed and in your PATH (download here)
PrinceXML is only needed to turn HTML into PDF. crown layout works on
PDFs you already have, so it runs without Prince installed.
my-book/
├── src/
│ ├── content/ # Markdown content files
│ │ ├── 00-title.md
│ │ ├── 01-intro.md
│ │ └── 02-chapter-one.md
│ ├── templates/ # Handlebars templates
│ │ ├── layout.html
│ │ └── partials/
│ │ ├── title.html
│ │ └── chapter.html
│ └── styles.css # Main stylesheet
├── crown.config.js # Configuration
└── dist/ # Generated files
├── book.html
└── book.pdf
Create a crown.config.js file in your project root:
import { defineConfig } from "@romello/crown";
export default defineConfig({
input: {
content: "src/content/**/*.md",
template: "src/templates/layout.html",
styles: "src/styles.css",
assets: "src/fonts", // Optional: copied to output directory
},
output: {
html: "dist/book.html",
pdf: "dist/book.pdf",
},
metadata: {
title: "My Book",
author: "Your Name",
subject: "A great book",
keywords: ["book", "crown"],
},
page: {
size: "5.5in 8.5in",
margins: {
top: "0.75in",
bottom: "0.75in",
inside: "0.75in",
outside: "0.5in",
},
},
});Content files are written in Markdown with YAML frontmatter:
---
title: Chapter One
subtitle: The Beginning
id: chapter-1
order: 1
---
# Chapter One
Your content here...The order field determines the sequence of content in your book.
Crown uses Handlebars for templating. Access your content and metadata in templates:
<!DOCTYPE html>
<html lang="{{metadata.lang}}">
<head>
<title>{{metadata.title}}</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
{{#each content}}
<section class="chapter" id="{{frontmatter.id}}">
<h1>{{frontmatter.title}}</h1>
{{{html}}}
</section>
{{/each}}
</body>
</html>{{markdown text}}- Render inline markdown{{json data}}- Pretty-print JSON{{formatDate date}}- Format dates{{eq a b}}- Equality check{{gt a b}},{{lt a b}}- Comparisons{{length array}}- Array length{{join array separator}}- Join array elements
Crown automatically generates an @page rule from your page config (size and margins). If your CSS already includes an @page rule, Crown will use yours instead.
Style your books with CSS, including Prince-specific properties:
/* @page is auto-generated from config — only add this if you need custom overrides */
@page {
size: 5.5in 8.5in;
margin: 0.75in;
}
@page :left {
@bottom-left {
content: counter(page);
}
}
@page :right {
@bottom-right {
content: counter(page);
}
}
h1 {
break-before: recto; /* Start chapters on right page */
break-after: avoid; /* Keep with following content */
}# Scaffolding
crown create my-book # Create a new project
# Development
crown dev # Start dev server
crown dev --port 3001 # Custom port
crown dev --no-open # Don't open browser
# Building
crown build # Build production PDF
crown build -c path/to/config.js # Use a specific config file
crown build --output path/to.pdf # Custom output path
crown build --verbose # Show detailed output
# Imposition
crown layout book.pdf # Impose onto press sheets
crown layout --list # Show formats and sheet sizes
# Utilities
crown watch # Watch and rebuild (no server)
crown preview:html # Open HTML in browser
crown preview:pdf # Open PDF in viewer
crown doctor # Check the environmentAll commands accept -c/--config <path> to specify a config file. This is useful for monorepos or projects with multiple books.
crown build gives you a PDF of finished pages, one after another. crown layout arranges those pages onto press sheets so that printing, folding and stapling produces a book that reads in order — the step usually called imposition.
crown layout book.pdf # quarter-letter zine, the default
crown layout book.pdf -f sixth -o sheets.pdf # six up on a letter sheet
crown layout books/*.pdf -o print/ # a whole edition at once
crown layout cover.pdf -f half -i cover # a 4-page wrap cover
crown layout eight.pdf -f magic -i magic # one sheet, fold and cutHow it works. A sheet is tiled into a grid of cells, one finished page each. Two side-by-side cells make a folio — a piece of paper with a fold between them, four pages once it's printed on both sides. Folios are cut apart, nested inside one another, and stapled through the fold. Only the grid changes between formats.
--format |
grid | page on letter | pages/sheet |
|---|---|---|---|
half |
2 × 1 | 5.5 × 8.5 | 4 |
quarter-portrait (default) |
2 × 2 | 4.25 × 5.5 | 8 |
quarter-landscape |
2 × 2 | 5.5 × 4.25 | 8 |
sixth |
2 × 3 | 4.25 × 3.67 | 12 |
eighth-landscape |
2 × 4 | 4.25 × 2.75 | 16 |
eighth-portrait |
4 × 2 | 2.75 × 4.25 | 16 |
magic |
4 × 2 | 2.75 × 4.25 | 8, one side |
Formats are named by the grid rather than the paper, so --sheet letter|legal|tabloid|a4|a3 gives the same layout at another size — -f quarter-portrait -s tabloid yields 5.5 × 8.5 pages.
--imposition picks what's being made: zine nests every folio into one stitched book (the default), cover imposes a 4-page wrap and repeats it to fill the sheet, and magic is the one-sheet eight-page zine you fold into eighths and cut through the middle.
Duplex matters. A fold runs vertically down the sheet, so the back must mirror left-to-right: that's the long edge on a portrait sheet and the short edge on a landscape one. The command prints which to use. If your printer only flips the other way, pass --duplex long|short and the backs are mirrored to compensate.
Other options: --signature <pages> splits a long book into several nested signatures instead of one thick one, and --marks draws trim and fold ticks at the sheet edges. Books that aren't a multiple of 4 are padded with blanks, and pages that don't exactly match the cell are scaled to fit rather than distorted.
Load external data into your templates:
// crown.config.js
export default defineConfig({
// ...
data: {
sales: "./data/sales.csv",
authors: "./data/authors.json",
stats: "./data/stats.yaml",
},
});Access in templates:
Add custom Handlebars helpers:
// src/templates/helpers.js
export default {
uppercase(str) {
return str.toUpperCase();
},
formatPrice(amount) {
return `$${amount.toFixed(2)}`;
},
};Reference in config:
export default defineConfig({
// ...
helpers: "src/templates/helpers.js",
});Crown supports custom marked extensions for features like footnotes, math, etc.:
export default defineConfig({
// ...
markdown: {
gfm: true, // GitHub Flavored Markdown (default: true)
breaks: false, // Convert \n to <br> (default: false)
extensions: [
"./src/extensions/footnotes.js", // Custom marked extensions
],
},
});Each extension file should export a valid MarkedExtension object.
docs/development.md covers the build, the test suite and the patterns this
codebase follows. In short: npm install, npm run dev for watch mode,
npm test before you push.
Issues and pull requests welcome.
MIT — by Romello Goodman.