Wit

Custom nodes / extending vocabulary

Core vocab is closed: 52 reserved names, not extensible. Everything else you add either by composition — a definition, no code — or through the opaque @node plus renderer dispatch. This page covers both sides: what an author writes and what a renderer does with it.

Three ways to extend

  • A definition. #name … name# is pure composition; the expander inlines it, and no renderer changes. Reach for this first.
  • The opaque node. @node |type X| survives expand as an ordinary NodeUse (name: 'node'), and your renderer dispatches on the type param. Reach for this when you need new rendering that composition can't express.
  • Core vocab. Fixed and not extensible; isReservedNodeName rejects any redefinition. Shown here only for contrast.

The opaque-node contract

@node needs a type param; all other params and the body survive into the expanded AST. The HTML renderer dispatches two ways:

  • type is itself a core-vocab name. The renderer re-emits that element. Verified: @node |type img| |src ./lamp.png| |alt …|<img src="./lamp.png" alt="The keeper&#39;s lamp">.
  • type is anything else. The renderer emits a generic <div> carrying every named param as data-*. Verified: @node |type chart| |layout horizontal| A body node@<div data-type="chart" data-layout="horizontal"><p> A body </p></div>.

The preferred authoring pattern

Raw @node is noisy in prose. Wrap it in a definition so authors get a natural name while the AST still carries type for the renderer:

#highlight ||content||
@node |type highlight| ::content:: node@
highlight#

@highlight Some emphasised text highlight@

The author writes @highlight …; the expanded AST carries type highlight for the renderer to dispatch on. Fixture 20-opaque-node/user-defined-wrapper.wit.

Every edge

  • Block form vs. self-closing. @node … node@ has a body; @node |type img| self-closes. Fixtures node-with-body and bare-node-passthrough.
  • Unknown types stay portable. An unknown type is round-trippable via data-*, so a document survives across renderers that don't know the type.
  • Names are unique. A duplicate non-additive #name throws E_DUPLICATE_DEFINITION; core names cannot be redefined at all.

Common mistakes

  • Trying to redefine @table or @h1 — they are reserved.
  • Expecting Markdown to render an opaque container — it emits the body only.
  • Reading data-wit-type — the attribute is data-type.

See also

Build your own renderer

· Rendering · AST reference (NodeUse, raw / frozen) · Resolve & expand.