Docouture

Custom AsciiDoc components

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.

Reference

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.
--
====
Placeholder card image
One

Every card in a block shares one aspect ratio.

Placeholder card image
Two

The image crops to fill; it is never letterboxed.

  • type= — no-image (default), image-landscape, image-square, or image-portrait.

  • columns= — space-separated breakpoint:count tokens (a bare number sets the base/mobile count); breakpoints are s, m, l only, up to 4 columns each. Defaults to "1 s:2 m:3" if omitted.

  • width= — content (default, matches the page’s text measure) or container (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.
--
====
Placeholder feature imagefeature placeholder dark

A slide is a media still, prose, and an optional call to action, in that order.

Learn more
Placeholder feature imagefeature placeholder dark

A call to action is optional — a slide without one just ends at its prose.

CTA

A single, full-width call-to-action band:

[cta]
====
A short pitch, plus one prominent action.

[.primary]
xref:main:quickstart.adoc[Get started]
====

A short pitch, plus one prominent action.

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
....

start

stop

Idle

Running

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
    }
  }
}
....
System Context View: Software SystemUser[Person]Software System[Software System]Uses

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>
....
Request receivedHandle requestDone

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
    }
  ]
}
....
Hello, Excalidraw

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
....

start

stop

Idle

Running

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; className is the deliberate opt-out when a whole column of cells is tokens and the chip would be noise): className

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.