Narrative
zigzag, markdown-prose, timeline, steps-panel and faq — sections that carry an argument.
zigzag
Alternating text and image rows. The standard way to explain three or four things in sequence without a wall of prose.
How it works
Point the engine at a folder
One config object names your content directory. Zellij reads everything else from what it finds there.
See the content model
Write sections, not components
A section is a YAML file with a type and some fields. Pages list the ones they want, in order.

type: zigzag
heading: How it works
rows:
- title: Point the engine at a folder
body: One config object names your content directory. Zellij reads **everything else** from what it finds there.
image: assets/example/wide-1.png
align: image-right
cta: { label: See the content model, href: /guide/content-model }
- title: Write sections, not components
body: A section is a YAML file with a `type` and some fields. Pages list the ones they want, in order.
image: assets/example/wide-2.png
align: image-leftbody accepts inline markdown — bold, italic, code spans and links. Not block
markdown: for that, use markdown-prose or a guide page.
markdown-prose
The escape hatch. Free-form prose inside marketing styling, for the page that needs a paragraph the tiles do not cover.
Why we built Zellij
Every product site we shipped started the same way: a fresh Next.js app, a fortnight of layout work, and a design that drifted from the last one within a quarter.
The content was never the hard part. The scaffolding was. So we moved the scaffolding into a package and left the content in a folder.
type: markdown-prose
heading: Why we built Zellij
background: tinted
body: |
Every product site we shipped started the same way: a fresh Next.js app, a
fortnight of layout work, and a design that drifted from the last one within
a quarter.
The content was never the hard part. The scaffolding was. So we moved the
scaffolding into a package and left the content in a folder.A .md or .mdx file under sections/ is this type without saying so:
---
background: tinted
---
## Why we build Acme
Operations teams deserve better than a spreadsheet with opinions.timeline
A dated sequence: company history, a roadmap, milestones, the chapters of a book. Rendered as an ordered list, because that is what it is.
The story so far
A layout copied between three products, and then finally extracted.
2024
The third copy-paste
Same header, same footer, same section stack, diverging by the week.
Q1 2026
One package, three sites
The layout moved into an engine and the products kept only their content.

Q3 2026
Documented by itself
This page renders through the engine it documents.
type: timeline
heading: The story so far
subhead: A layout copied between three products, and then finally extracted.
items:
- date: 2024
title: The third copy-paste
body: Same header, same footer, same section stack, diverging by the week.
icon: sprout
- date: Q1 2026
title: One package, three sites
body: The layout moved into an engine and the products kept only their content.
icon: package
image: assets/example/wide-2.png
- date: Q3 2026
title: Documented by itself
body: This page renders through the engine it documents.
icon: book-opendate is free text, not a date type — 1984, Q3, Chapter one and
Some time in the eighties all belong. A bare year is accepted and
stringified, since YAML hands 2019 over as a number.
Items reveal from the side under subtle and expressive, which reads as the
sequence assembling itself as you scroll.
steps-panel
Numbered steps on one side, the result they produce on the other. For a process where the outcome is a shape somebody needs to recognise — a folder, a schema, a directory tree — and a list of instructions alone would leave them guessing what they are working toward.
Four commands, and a folder that explains itself
The steps say what you run. The panel says what you end up with, so the two can be checked against each other without leaving the section.
Write the folder
zel new docs my-siteputs eight files on disk — identity, a menu, a page and a guide. It writes files and exits; nothing is started.Watch it while you edit
zel dev my-sitemounts the folder into the engine and serves it. Every save is on screen before you have switched windows.Check before you build
zel check my-sitereads the whole folder and reports what is wrong with it, in about a second, without starting the application.Build what you deploy
zel build my-siteproduces either a directory of static files or a container image, from the same content.
my-site/
├── site.yaml ← identity and theme
├── menu.yaml ← the bar
├── pages.yaml ← pages, as section lists
├── sections/
│ └── home/hero.yaml ← one band of content
└── 01-guide/
├── _sidebar.yaml ← order and grouping
└── index.mdx ← a page
Nothing about the site lives outside this folder, which is why it travels between a laptop, a pipeline and a container unchanged.
type: steps-panel
heading: Four commands, and a folder that explains itself
subhead: >-
The steps say what you run. The panel says what you end up with, so the two
can be checked against each other without leaving the section.
steps:
- title: Write the folder
body: >-
`zel new docs my-site` puts eight files on disk — identity, a menu, a page
and a guide. It writes files and exits; nothing is started.
- title: Watch it while you edit
body: >-
`zel dev my-site` mounts the folder into the engine and serves it. Every
save is on screen before you have switched windows.
- title: Check before you build
body: >-
`zel check my-site` reads the whole folder and reports what is wrong with
it, in about a second, without starting the application.
- title: Build what you deploy
body: >-
`zel build my-site` produces either a directory of static files or a
container image, from the same content.
terminal:
title: my-site
body: |
my-site/
├── site.yaml ← identity and theme
├── menu.yaml ← the bar
├── pages.yaml ← pages, as section lists
├── sections/
│ └── home/hero.yaml ← one band of content
└── 01-guide/
├── _sidebar.yaml ← order and grouping
└── index.mdx ← a page
caption: >-
Nothing about the site lives outside this folder, which is why it travels
between a laptop, a pipeline and a container unchanged.The numbers are drawn from the list's own counter and never authored. Writing
01, 02 into the titles works right up until a step is inserted in the
middle, and a screen reader would read every number twice.
The panel is sticky beside the steps on a wide viewport, so it is still there at the last step; stacked on a narrow one, where pinning it would cover the steps below.
The panel takes one of three things: visual for an image, terminal for a
transcript, or diagram for Mermaid source. The last is what the section was
really built for — a sequence diagram beside a numbered legend, where the
numbers on the arrows are the numbers down the list:
type: steps-panel
media: left
diagram: |
sequenceDiagram
autonumber
participant C as CI Runner<br/>ubuntu-latest
participant S as GitHub Secrets<br/>encrypted store
S->>C: inject SGIT_KEY
C->>C: run-secure: decrypt
steps:
- title: inject SGIT_KEY
body: GitHub decrypts the repository secret into the job's environment.
- title: run-secure — decrypt
body: Derives the key with scrypt and decrypts every manifest-listed blob.Up to twelve steps. The panel is sticky so it survives a long legend, but a legend that outruns a screen twice over has become an article, and an article belongs in a guide.
faq
An accordion of question and answer pairs.
Reasonable questions
Do I have to write React?
No. A wrapper app is three files and under thirty lines, and none of it changes as your site grows.
Can I add my own section type?
Yes — registerSection takes a schema and a component, and the renderer picks it up.
What happens if a link points at nothing?
The build fails and names the file, rather than shipping a 404.
type: faq
heading: Reasonable questions
items:
- question: Do I have to write React?
answer: >-
No. A wrapper app is three files and under thirty lines, and none of it
changes as your site grows.
- question: Can I add my own section type?
answer: Yes — `registerSection` takes a schema and a component, and the renderer picks it up.
- question: What happens if a link points at nothing?
answer: The build fails and names the file, rather than shipping a 404.Built on <details>, so it expands and collapses with no JavaScript at all and
is keyboard operable for free. Answers accept inline markdown.
