Widget Blocks
@Doc Widget Blocks — Semantic Reference
Companion to Block Syntax Specification §8 (Widget Blocks). The grammar lives there; this document covers meaning, usage, and the open edges of
@tabs,@tab, and@mermaid.
0. Table of Contents
- 1. Design Philosophy
- 2. Why Widget Blocks Exist
- 3. Shape Comparison
- 4. Node Reference
- 5. AST Representation
- 6. Renderer Independence
- 7. AI Generation Stability
- 8. Design Principle
1. Design Philosophy
Widget Blocks cover content that needs more structure than a Structural Block but doesn't fit the shared title+content shape of Container or Callout Blocks. README's Node Taxonomy names only "Core Nodes" and "Semantic Nodes" at the top level; Widget Blocks is a category introduced at the Block Syntax Specification level (see §2 Document AST Structure) for block-level components whose grammar is bespoke to the widget itself.
@tabs / @tab @mermaidLike Structural Blocks (see Structural Blocks §1), the two members of this family don't share one shape: @tabs is a restrictive container that only accepts @tab children, and @mermaid is an opaque, unparsed content domain. What unites them under "Widget" is that both render as a self-contained interactive or embedded unit — not that they share a grammar.
2. Why Widget Blocks Exist
Some content isn't a paragraph, isn't a collapsible section, and isn't a severity-graded callout — it's a small, self-contained component with its own internal rules: a tabbed switcher, an embedded diagram language. Markdown has no native answer for either; authors fall back to raw HTML (<div class="tabs">...), framework-specific MDX components, or fenced code blocks that a renderer has to special-case by matching the language string ( ``mermaid `).
@Doc gives each of these an explicit node instead of overloading an existing primitive:
Meaning
↓
AST Node
↓
Renderer Adapter
↓
Output TargetA parser can find every @tabs or @mermaid in a document directly, without scanning for a class="tabs" attribute or matching a fenced-code-block language tag against a known list.
3. Shape Comparison
| Node | Modifier / Option | Content | Shape notes |
|---|---|---|---|
@tabs | — | { @tab } only — not generic block-content | only node whose content is restricted to a single child node type |
@tab | (text), required, not optional | block-content | not part of block-node; valid only inside @tabs (see Tabs) |
@mermaid | — | raw-block-content | unparsed — same idea as @code, but without even a (language) slot |
No two rows are identical — the same "no shared shape" observation Structural Blocks §3 makes about @heading, @code, @table, and @hr.
4. Node Reference
Tabs
@tabs accepts only one or more @tab children — no other block-node and no bare text:
@tabs[
@tab(JavaScript)[
...
]
@tab(Python)[
...
]
@tab(Rust)[
...
]
]@tab(title)[content]—titleis the tab's display label, andcontentis fullblock-content(any block-node or inline-stream may appear inside).- Unlike
(title)on Container Blocks (@details,@card) or Callout Blocks,@tab's(text)is not optional in the EBNF (tab = "@tab" , "(" , text , ")" , block-content) — there is no bracketed[ ... ]around it. A tab with no label has no defined form. - If
@tabs[...]contains anything other than@tab(bare text, or another block-node), the parser MUST treat it as a syntax error in Strict Mode, or auto-correct/flag it in Editor Mode (see Block Syntax Specification §8 Tabs).
@tab is deliberately excluded from the block-node alternation in the EBNF (see Block Syntax Specification §3, the note directly above metadata =), so it cannot appear at the document's top level or inside another block's block-content — only inside @tabs. This mirrors the Opaque Domain idea behind @raw (see Inline Syntax Specification §9): a piece of syntax that is only meaningful inside one specific context.
Mermaid
@mermaid[
graph TD
A --> B
]@mermaid's content is raw-block-content — the same unparsed-body idea @code uses (see Structural Blocks: Code), except @mermaid has no (language) slot at all, since the diagram language is implied by the node name itself. Once the parser sees @mermaid[, nothing inside is treated as @Doc syntax — no @bold, no @link, no nested block — until the matching ].
Use for: any diagram expressed in Mermaid's own text syntax (flowcharts, sequence diagrams, state diagrams, etc.). The renderer is responsible for actually running a Mermaid engine, or falling back to a plain code block, on targets that can't render diagrams.
5. AST Representation
Example:
@tabs[
@tab(JavaScript)[
@code(js)[console.log("hi")]
]
@tab(Python)[
@code(py)[print("hi")]
]
]Document
└── BlockNodes
└── WidgetNodes
└── TabsNode
├── TabNode
│ ├── Title
│ │ └── "JavaScript"
│ └── Content
│ └── CodeNode (js) → "console.log(\"hi\")"
└── TabNode
├── Title
│ └── "Python"
└── Content
└── CodeNode (py) → "print(\"hi\")"@mermaid, by contrast, produces a single leaf node whose body is stored as an opaque string, the same way @code does:
Document
└── BlockNodes
└── WidgetNodes
└── MermaidNode
└── RawContent
└── "graph TD\nA --> B"Because @tab titles are a discrete Title field rather than mixed text, a tool can list every tab label in a @tabs block without re-parsing content — the same benefit Container Blocks get from a dedicated Title field (see Container Blocks §6).
6. Renderer Independence
Source:
@tabs[
@tab(npm)[npm install]
@tab(pnpm)[pnpm add]
]Web: a native tab-switcher component, chosen by the adapter.
Terminal:
[npm] npm install
[pnpm] pnpm addSource:
@mermaid[
graph TD
A --> B
]Web: a rendered diagram (Mermaid engine, or an SVG).
Terminal / plain text: a fenced code block containing the raw Mermaid source, since no target can be assumed to render diagrams.
7. AI Generation Stability
Without a dedicated node, models express tab switchers through inconsistent, framework-specific markup — a <div class="tabs"> with hand-rolled JS, an MDX <Tabs><Tab> pair, or a heading-per-tab convention that a renderer has to reverse-engineer. @tabs[ @tab(title)[content] ... ] gives the model exactly one deterministic shape, and restricting @tabs's children to @tab alone (§4) means a model cannot accidentally produce a @tabs block containing loose text that a parser would have to guess how to handle.
@mermaid's opaque body removes a different failure mode: because nothing inside @mermaid[...] is parsed as @Doc syntax, a model generating Mermaid source (which has its own -->, [], and {} conventions) never has those characters misinterpreted as @Doc brackets.
8. Design Principle
Widget Blocks follow the same rule as the rest of @Doc:
Semantic First, Layout LaterA tab switcher is not a <div> with JavaScript. A diagram is not a fenced code block that happens to say mermaid. Each node is defined by what it is, not by how any single renderer happens to draw it today.
@Doc