This site’s own blocks, registered by @inditextech/docouture-asciidoc-extensions and
documented in full in the docouture-writing-docs-pages skill’s
reference/docouture-blocks.md. Every one of these degrades to plain, readable HTML
with JavaScript disabled. See AsciiDoc basics for plain
AsciiDoc first, and Integrations for the build-time
capabilities (diagrams, syntax highlighting) mentioned below, in full. Same pattern
throughout: raw markup first, then the same markup rendered live.
Tabs
A switcher for equivalent alternatives. Each [tabs] block is independent — picking a
tab in one never affects another further down the page.
[tabs]
--
[tab,label="pnpm"]
****
[,bash]
----
pnpm add some-package
----
****
[tab,label="npm"]
****
[,bash]
----
npm install some-package
----
****
[tab,label="yarn"]
****
[,bash]
----
yarn add some-package
----
****
--
pnpm add some-package
npm install some-package
yarn add some-package
A second, independent set:
[tabs]
--
[tab,label="pnpm"]
****
[,bash]
----
pnpm run dev
----
****
[tab,label="npm"]
****
[,bash]
----
npm run dev
----
****
--
pnpm run dev
npm run dev
Cards
Text-only — no image, no open block needed:
[cards]
====
[card]
.xref:main:quickstart.adoc[Quickstart]
Scaffold, build, publish — the smallest path to a working result.
[card,subheader="Reference"]
.xref:main:guides-adding-a-page.adoc[Adding a page]
Create a file, add it to nav.adoc, done.
[card,icon="grid-3x3"]
.xref:main:architecture.adoc[Architecture]
This one carries a header icon — `icon=` and `subheader=` share one header row and
combine fine on the same card, they aren't alternatives to each other.
====
Scaffold, build, publish — the smallest path to a working result.
Create a file, add it to nav.adoc, done.
This one carries a header icon — icon= and subheader= share one header row and
combine fine on the same card, they aren’t alternatives to each other.
icon= takes a bare icon name (Lucide’s own, e.g. store), and only renders if that
icon has a generated CSS mask — see Icon gallery for the full,
working set. A per-card labels= attribute (comma-separated) renders a separate row of
small grey chips, independent of subheader=.
With images, a fixed square aspect, four columns, let out to the container’s full width —
the same shape the landing’s own quicklinks use (ROOT:card-placeholder.png reused
across modules via a cross-module image resource ID):
[cards,type=image-square,columns="1 s:2 m:4",width=container]
====
[card,subheader="One"]
.xref:main:quickstart.adoc[First]
--
image::ROOT:card-placeholder.png[Placeholder card image]
Every card in a block shares one aspect ratio.
--
[card,subheader="Two"]
.xref:main:index.adoc[Second]
--
image::ROOT:card-placeholder.png[Placeholder card image]
The image crops to fill; it is never letterboxed.
--
====

Every card in a block shares one aspect ratio.

The image crops to fill; it is never letterboxed.
-
type=—no-image(default),image-landscape,image-square, orimage-portrait. -
columns=— space-separatedbreakpoint:counttokens (a bare number sets the base/mobile count); breakpoints ares,m,lonly, up to 4 columns each. Defaults to"1 s:2 m:3"if omitted. -
width=—content(default, matches the page’s text measure) orcontainer(full page width, as above).
Accordion
Groups a run of [%collapsible] items with role=group semantics. %single-open closes
whichever other item was open; without it, items are independent.
[accordion%single-open,aria-label="Single-open example"]
--
.First question?
[%collapsible]
====
First answer.
====
.Second question?
[%collapsible]
====
Second answer. Opening this closes the first.
====
--
First question?
First answer.
Second question?
Second answer. Opening this closes the first.
Grouped, but independent (the default — no %single-open):
[accordion,aria-label="Multiple-open example"]
--
.Can both of these be open at once?
[%collapsible]
====
Yes — this group has no `%single-open`, so each item toggles independently.
====
.Is this still one accessible group?
[%collapsible]
====
Yes — `aria-label=` (or a block `.Title`) names the group as a whole.
====
--
Can both of these be open at once?
Yes — this group has no %single-open, so each item toggles independently.
Is this still one accessible group?
Yes — aria-label= (or a block .Title) names the group as a whole.
Feature tabs
A media-plus-prose switcher for a handful of top-level capabilities, same block the landing’s "Key features" section uses:
[feature-tabs]
====
[feature,label="With a call to action"]
--
image::ROOT:feature-placeholder.png[Placeholder feature image]
image::ROOT:feature-placeholder-dark.png[role=dark]
A slide is a media still, prose, and an optional call to action, in that order.
[.cta]
xref:main:quickstart.adoc[Learn more]
--
[feature,label="Without one"]
--
image::ROOT:feature-placeholder.png[Placeholder feature image]
image::ROOT:feature-placeholder-dark.png[role=dark]
A call to action is optional — a slide without one just ends at its prose.
--
====
Diagrams
[mermaid], [plantuml], [graphviz] and over a dozen more diagram languages (see
@inditextech/docouture-asciidoc-extensions’ `lib/kroki-config.js for the full,
live-verified list) render as real diagrams via a self-hosted Kroki
service — a literal block (four dots, not a fenced code block) styled with the diagram
language’s name:
[mermaid]
....
stateDiagram-v2
[*] --> Idle
Idle --> Running : start
Running --> Idle : stop
....
This feature is enabled by default in this starter (see antora-playbook.yml’s
`kroki-enabled/kroki-diagram-types attributes) — it needs Docker
available, since the
build starts a self-hosted Kroki service the first time a build needs it, no manual step
required (it isn’t stopped automatically either — run docouture teardown kroki once you’re
done with it). Run docouture eject kroki if you ever need to customize the container
definition, or comment the two attributes back out to turn the feature off entirely — a
block like the one above then renders exactly as plain AsciiDoc already would: the raw
diagram source, as literal text, same as the fenced listing shows it above.
Positional shorthand and diagram-specific options
The classic asciidoctor-diagram/asciidoctor-kroki [type,target,format] positional
form works alongside the named format= attribute already shown above — the second
comma-separated value is a target (accepted but otherwise unused: this extension
always inlines, never writes a named file to disk), the third is the format:
[plantuml,connectivity-flow,png]
....
Alice -> Bob : request
Bob --> Alice : response
....
Any named attribute beyond the built-in set (target, width, height, format,
role, title, caption, …) is a Kroki diagram-specific option, forwarded to Kroki as
a Kroki-Diagram-Options-<key> HTTP header — exactly as the real asciidoctor-kroki
extension forwards them. Structurizr’s view-key selects which view of a
multi-view workspace to render:
[structurizr,view-key=SystemContext]
....
workspace {
model {
user = person "User"
system = softwareSystem "Software System"
user -> system "Uses"
}
views {
systemContext system "SystemContext" {
include *
autoLayout
}
}
}
....
Styling
Mermaid diagrams are themed to match this site
automatically — square corners, black-on-white, body typography — via a
%%{init: {…}}%% directive this extension prepends to the diagram’s own source
before it ever reaches Kroki, not via CSS. Write your own %%{init…}%% as the
diagram’s first line to opt out and take full control of Mermaid’s own theming
instead. Every other diagram type’s font is still normalized via CSS (safe — a tool’s
typeface choice carries no meaning). BPMN's own rounded
task-box corners could not be un-rounded by any means found — bpmn-js hardcodes that
radius; this is a real, currently-unfixed limitation, not an oversight.
The card framing a diagram follows this site’s own light/dark theme, same as any other
card — but the diagram’s own canvas underneath it is always a fixed white, regardless of
theme: diagram tools don’t agree on whether they draw a background at all (GraphViz
bakes its own opaque white one; Mermaid and PlantUML bake none), so a fixed canvas is
what makes every diagram read the same rather than depending on that inconsistency —
and it means a diagram’s own colors never need inverting for dark mode, avoiding the
distortion that would cause for anything genuinely colorful (a PlantUML skinparam
palette, a Structurizr diagram’s own color-coded boxes, an Excalidraw scene).
Output formats
[mermaid,format=png] (or any other diagram language and format Kroki’s own server
accepts for it — see kroki-config.js’s `FORMAT_SUPPORT for the exact, live-verified
matrix; it varies per type, not a blanket rule) renders a raster image instead of
inline SVG:
[mermaid,format=png]
....
stateDiagram-v2
[*] --> Idle
Idle --> Running : start
Running --> Idle : stop
....
Embedded as a plain <img> with a base64 data: URI — no extra file written, no extra
HTTP request. Reach for this only when you actually need a raster image (an export, an
email, a renderer that can’t handle inline SVG); the inline-SVG default stays sharper at
every zoom level and lets a reader select/search the diagram’s own text, neither of
which a raster format can do. Beyond png: jpeg (jpg also accepted), pdf
(embedded as an <embed>, since a browser can’t display a PDF through <img>) and
base64 all render the same way. An unsupported type/format combination, or a typo’d
format= value, falls back to svg with a build warning, the same degrade-not-fail
posture as an unknown kroki-diagram-types entry.
txt/atxt/utxt are different in kind, not just encoding — Kroki renders these as a
literal ASCII-art-style text representation, not an image at all:
[plantuml,format=txt]
....
Alice -> Bob : request
Bob --> Alice : response
....
,-----. ,---.
|Alice| |Bob|
`--+--' `-+-'
| request |
|-------------->|
| |
| response |
|<- - - - - - - |
,--+--. ,-+-.
|Alice| |Bob|
`-----' `---'
BPMN
[bpmn] renders BPMN 2.0 XML (the same format bpmn.io and most process-modeling tools
export). Like mermaid and excalidraw, it needs its own headless-Chrome companion
container — the bundled kroki-compose.yml already includes one:
[bpmn]
....
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
id="definitions" targetNamespace="http://bpmn.io/schema/bpmn">
<process id="process_1" isExecutable="false">
<startEvent id="start" name="Request received"/>
<task id="task" name="Handle request"/>
<endEvent id="end" name="Done"/>
<sequenceFlow id="flow_1" sourceRef="start" targetRef="task"/>
<sequenceFlow id="flow_2" sourceRef="task" targetRef="end"/>
</process>
<bpmndi:BPMNDiagram id="diagram">
<bpmndi:BPMNPlane id="plane" bpmnElement="process_1">
<bpmndi:BPMNShape id="start_di" bpmnElement="start">
<omgdc:Bounds x="100" y="100" width="36" height="36"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="task_di" bpmnElement="task">
<omgdc:Bounds x="200" y="78" width="100" height="80"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="end_di" bpmnElement="end">
<omgdc:Bounds x="360" y="100" width="36" height="36"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="flow_1_di" bpmnElement="flow_1">
<omgdi:waypoint x="136" y="118"/>
<omgdi:waypoint x="200" y="118"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="flow_2_di" bpmnElement="flow_2">
<omgdi:waypoint x="300" y="118"/>
<omgdi:waypoint x="360" y="118"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
....
Excalidraw
[excalidraw] renders an Excalidraw scene (the JSON a .excalidraw file, or
excalidraw.com's own "Save to…" export, contains). Unlike
bpmn, this type does need its own companion — the bundled kroki-compose.yml
includes an excalidraw service alongside mermaid for exactly that reason:
[excalidraw]
....
{
"type": "excalidraw",
"version": 2,
"elements": [
{
"type": "rectangle",
"id": "rect1",
"x": 100,
"y": 100,
"width": 200,
"height": 100,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"seed": 1
},
{
"type": "text",
"id": "text1",
"x": 130,
"y": 135,
"width": 140,
"height": 25,
"text": "Hello, Excalidraw",
"fontSize": 20,
"seed": 2
}
]
}
....
Zoom
Any diagram — or a plain image:: block, which already gets this for free from
Asciidoctor’s own role-to-class handling — marked role=zoom-in gets a click-to-zoom
affordance: a zoom-in cursor and a subtle hover tint for a mouse, a small persistent
badge for touch, and clicking/tapping opens a fullscreen overlay with a bigger view.
Esc, the close icon, or clicking the backdrop all dismiss it — static, no pan/pinch-zoom,
just a bigger still image:
[mermaid,role=zoom-in]
....
stateDiagram-v2
[*] --> Idle
Idle --> Running : start
Running --> Idle : stop
....
Inline macros: label: and mono:
label:grey[Default] label:red[Blocked] label:orange[Pending] label:green[Stable]
label:blue[Info] label:purple[Beta] label:pink[New] label:teal[Docs] label:white[White]
mono:[className]
Default Blocked Pending Stable Info Beta New Docs White
mono: is plain monospaced text with no code-chip styling — for a table cell whose
entire content is a bare token (see AsciiDoc basics's
Tables section, which uses backtick code instead; is the
deliberate opt-out when a whole column of cells is tokens and the chip would be
noise): classNameclassName
Table and video sizing attributes
table-width= and nowrap-cols= are demonstrated in
AsciiDoc basics's Tables section. The one remaining sizing
attribute, for a video block, is not rendered live on this page (it would embed a
real, unrelated third-party video) — the syntax is:
video::VIDEO_ID[youtube,640,360]
640,360 both caps the embed’s width and locks its aspect ratio at any narrower
viewport; width alone caps the width and leaves the ratio at the 16:9 fallback.

