Skip to content

📐 Slide Layouts

Declaring a slide’s layout does two things today: it types the slide, and it tells the linter which content schema to validate it against. It also puts the type in the generated HTML, so a theme can style it.

It also changes how the slide looks — for nine of them. Since ziradocs/toolchain#254, the generator ships a CSS file per covered layout, keyed on the data-slide-type attribute it emits. The rest render like a content slide. See Which layouts have styling below for exactly which is which — the table is the contract, not a description of intent.

Setting a layout types the slide and, when that layout has a schema, tells the linter which content rules to check it against. There are exactly two ways to set one, one per mode.

Flex mode — a layout: block before the heading

Section titled “Flex mode — a layout: block before the heading”
---
layout: hero
---
# Welcome to Our Product
## Revolutionary solutions for modern businesses

The block goes immediately before the slide’s heading. Only layout is read from it: any other key is reported as having no effect. An explicit layout: also overrides the default rule that types the deck’s first # heading as the title slide.

SLIDE testimonial
title: "What our customers say"
TEXT
"This product changed our business completely."
- Jane Smith, CEO

The type goes on the SLIDE line itself. It is not a property inside the block — writing layout: "testimonial" under SLIDE intro is an error (Unknown content block property).

23 layout names are recognized; 19 of them have a schema. A name outside these 23 still types the slide, but the linter warns (LAYOUT_UNKNOWN) because nothing validates it — that is how a typo like comparision surfaces.

The four without a schema (chapter, cover, intro, with_directive) are accepted and mapped to a template by the generator; they simply have no content rules of their own, so nothing about their content is checked.

What the linter actually enforces from a schema is the element count and the forbidden-element list — both as warnings — plus a rule of the layout’s own, which 17 of the 19 schemas have (title_slide and default have none). Min / Max below is the element count, ∞ meaning no maximum.

The schema also declares required, optional and allowed properties. Those three are not evaluated by anything — they are metadata inside the struct, not rules (ziradocs/toolchain#256) — so they are not listed here. The only property checks that exist are three bespoke ones, listed under What fails the build below.

Layout Min / Max elements Forbidden elements
before_after 2 / 4 code, chart
call_to_action 1 / 3 code, table, chart, mermaid
chapter (no schema) —
closing 0 / 3 code, table, chart, mermaid, map
code_example 1 / 4 table, chart, map
comparison 2 / 4 code, mermaid, chart
content 1 / ∞ —
cover (no schema) —
dashboard 1 / 6 code, mermaid
default 0 / ∞ —
feature_showcase 2 / 6 code, table
hero 0 / 3 code, table, chart
intro (no schema) —
pricing 1 / 4 code, chart, mermaid
process 2 / 6 code, table, chart
section 1 / 3 code, table, chart, map
stats 1 / 3 code, mermaid
team 1 / 8 code, chart, table
testimonial 1 / 3 code, table, chart
timeline 2 / 6 code, table, chart
title 0 / ∞ text, code, points, table, image
title_slide 0 / 1 text, code, points, table
with_directive (no schema) —

Those per-layout rules are the interesting half: a testimonial is checked for a quote and an author, a code_example for a code block, a dashboard for metrics or a chart. See llm-kit/validation-checklist.md §5 for the rule IDs.

Almost everything a layout can report is a warning. Exactly four diagnostics are errors, and they come from four layouts:

Layout Error Condition
title LAYOUT001 neither heading nor title is set
content LAYOUT004 the slide has no elements at all
code_example LAYOUT008 the slide has no code block
hero LAYOUT009 title is not set

Those are also the only property checks in the linter, which is why SLIDE comparison without a title lints clean while SLIDE hero without one fails (ziradocs/toolchain#256). Every other layout reports warnings only, so a mismatched layout is noisy but still builds.

Read the row before declaring a layout. Those bounds were calibrated for strict mode, where each declared block is one element; flex prose reaches them faster, so layout: testimonial on a slide with eight elements trades a clean content slide for a pile of warnings (ziradocs/toolchain#253).

---
layout: hero
---
# Main Title
## Subtitle
Regular content goes here. It is parsed and validated as a `hero` slide; the arrangement on
screen is the theme's job.
SLIDE feature_showcase
title: "New Feature: Smart Analytics"
TEXT
# New Feature: Smart Analytics
Our latest feature provides intelligent insights.
IMAGE "./screenshots/analytics-dashboard.png" "Analytics Dashboard Screenshot"
POINTS
- Real-time data processing
- Automated insights generation
- Custom report building

<<quiz>> and <<poll>> are real elements since ziradocs/toolchain#198. They carry a YAML body and work in both dialects and both modes:

<<quiz>>
question: "Which type of ML would you use for spam detection?"
options:
- "Unsupervised learning"
- "Supervised learning"
- "Reinforcement learning"
answer: 1
explanation: "We have labelled examples of spam and legitimate mail."
<<end>>

answer is 0-based: answer: 1 selects the second option. See Quizzes and polls for the full key list and what the rendered output does.

Requires v2.32.4 or newer, and the two dialects fail differently in v2.26.0. In strict it is a hard error — Unknown content block property: columns and the build stops. In flex, which is the default, the key is dropped with no diagnostic at all: the build reports zero warnings and the slide simply renders without the option.

A layout can carry options that change how its content is arranged (ziradocs/toolchain#255). They are declared per layout: writing one on a layout that does not accept it is reported, not silently ignored.

Option Layouts that accept it Values
columns comparison, stats, dashboard, call_to_action an integer from 1 to 4
align hero, testimonial, call_to_action left or center

Flex puts them in the same block as layout:. The order inside the block does not matter, and the keys go at the top level — the block is a list of key: value lines, not nested YAML:

---
layout: comparison
columns: 2
---
## Two side by side
:::info
The first option, with its points.
:::
:::tip
The second one, beside it rather than under it.
:::

Strict puts them as properties under the SLIDE line, like title::

SLIDE comparison
title: "Two side by side"
columns: 2

A value outside its range is reported (FLEX004 in flex, a parse error in strict) and the option is dropped — never applied halfway. An option written on a layout that does not accept it is reported per slide, naming that slide’s layout and what it does accept.

They travel in the AST as layout_config, and the HTML emits them as data-layout-columns / data-layout-align, so a theme can react to them in pure CSS:

.slidelang-slide[data-slide-type="comparison"][data-layout-columns="2"] .slidelang-content-elements {
display: grid;
grid-template-columns: repeat(2, 1fr);
}

Any other key in that block still has no effect and is still reported (FLEX002 in flex, a hard error in strict).

A theme can style a layout through the selectors the generator emits (see below). The --slidelang-layout-* variables shown in earlier versions of this page were never read by anything and no longer appear anywhere; the layout CSS that ships today uses the ordinary theme tokens. Those tokens carry the slidelang- prefix in the bundle — the layout files are written against --primary-color and the loader rewrites them, so what a theme declares and redefines is --slidelang-primary-color, exactly as every theme in the repo already does. Declare whatever variables your own CSS uses:

{
"name": "corporate-theme",
"variables": {
"--hero-bg": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
}
}
.slidelang-slide[data-slide-type="hero"] {
background: var(--hero-bg);
}

Every slide carries its layout in two places in the generated HTML:

<div class="slidelang-slide slidelang-hero-slide" data-slide-type="hero">

So a theme can target a layout either way:

.slidelang-slide[data-slide-type="hero"] { /* … */ }
.slidelang-hero-slide { /* … */ }

The bundled CSS keys on the attribute, not the class: data-slide-type is the block type verbatim, so it is immune to the class-namespacing pass the generator runs over the template. Your own theme CSS can use either.

A layout only loads its CSS when a slide actually uses it, so a deck pays for nothing it does not write.

All 23 recognized names, and nothing else:

Layouts What you get
Styled comparison, stats, dashboard, testimonial, hero, call_to_action, timeline, process, code_example Its own CSS file, written against the shape that layout actually has in real decks
Base-styled title, title_slide, cover, intro, section, chapter, closing, end The base stylesheet and the themes dress these; no separate file
Content-styled content, default, with_directive The ordinary content slide — this is the baseline everything else is compared against
Not styled yet before_after, feature_showcase, team, pricing Renders like a content slide

The four in the last row are typed and validated like any other layout; only the appearance is missing. before_after and feature_showcase need the template to group a heading with the elements that follow it, which it does not do today — a split-screen before_after cannot be expressed with the current DOM. team and pricing can be written as a table today, which is what the example decks do, so a dedicated stylesheet has not been worth its weight yet.

title_slide, cover and intro are aliases of title, and chapter of section: they carry their family’s class, so they look identical to it.

A layout without CSS is not an error. It types the slide and picks the linter’s schema, and renders like a content slide. Nothing is lost.

There is no layout inheritance (extends:), no expression-evaluated layout (layout: "{{ device == 'mobile' ? … }}"), and no way to nest several layouts inside one slide. None of these parse.

For composition inside a slide, use what exists: <<grid>> / <<column>> for side-by-side content, and ::: special blocks for callouts. See Grid and Column Layouts.

Shared visual defaults across decks belong to the theme system, not to per-slide metadata.

  1. Pick the layout that matches the content, not the one that looks nicest — the linter validates the slide against that layout’s schema, so a mismatch shows up as warnings.
  2. Read the schema before declaring a layout. Several cap how many elements a slide may hold, so layout: testimonial on a slide with eight elements trades a clean content slide for a pile of warnings.
  3. Check the result with slidelang build --lint-only before shipping a deck.

Which layout fits which content. Remember this only affects typing and validation today, not appearance.

Content Type Recommended Layout Alternative
Opening slide hero call_to_action
Product features feature_showcase comparison
Team introduction team testimonial
Data presentation stats dashboard
Process explanation process timeline
Case study before_after testimonial
Pricing pricing comparison
Technical demo code_example feature_showcase
Call to action call_to_action hero

One neighbouring page has not been reconciled with this one yet. Directives & Configuration documents a layout_override: slide property the parser rejects, along with style_class= / custom_css= attributes and ::::include directives that need the same check. Tracked in ziradocs/website#10 — until that lands, this page is the one to trust on layouts.

Specialized Layouts has been reconciled: it now says per layout what its CSS actually does, and which layouts have none.