Parameters & the invocation forms
A parameter is metadata that rides alongside a node's content — a link's address, a card's status, a figure's caption. Wit gives you five syntaxes for passing parameters, and they all bind to the same names. The guiding principle is the form you open with is the form that closes.
A single definition — here a card that names a title and a status — can be filled every way, and every buildable form renders identically.
#card ||title, status|| Title ::title:: / Status ::status:: card#
@card { title - Alpha, status - draft } ~ record-arg (self-closing) @card { title: Alpha, status: draft } ~ record-arg, colon separator @card title:Alpha status:draft card@ ~ colon-scatter (one-line body) @card ~ form-fill: the body IS the params title: Alpha status: draft card@ @card(title Alpha, status draft) ~ parens (self-closing) @card(title: Alpha, status: draft) ~ parens, colon separator @card |title Alpha| |status draft| card@ ~ pipes (carries a body)
All render Title Alpha / Status draft. (The fixture folder
26-all-param-forms collects these; its combined showcase file intentionally
fails to build, so lean on the individual forms.)
Choosing a form
- Structured, no body? Prefer record-arg with
a colon —
@card { title: X }. - Tag a node mid-sentence? Colon-scatter —
@figure src:lamp caption:"the lens" figure@. - Many fields, or a multi-line value? Use a form-fill body.
- One or two short params inline? Reach for parens —
@badge(tone good). - Need a body, or switching a value mid-body? Only pipes compose with body content — but they are the noisiest form and the one prone to greedy-bind, so avoid them otherwise.
The shared slot grammar
Record-arg, colon-scatter, parens, and pipes all read each slot with the same grammar:
- Positional — a single token with no space:
|full|binds a value with no name. It is a value, not a flag. - Named —
key value: the first word is the key, the rest is the value, which may be several words.|mood calm|setsmoodtocalm. - Named, multi-word key — join words with a spaced hyphen
-:|background colour - dark slate|has keybackground colour. An embedded hyphen with no surrounding spaces stays in the value —|well-known|. - Flag — a trailing
!marks a boolean-style flag with an empty value:|full width!|. A!in the middle of a value is just punctuation.
A value that is exactly "quoted" is unquoted, with "and \ recognised. Every non-flag value is then type-probed — run through the scalar classifier — so a number, record, or
collection passed through any form becomes a real typed value you can
iterate, compare, or reach into. See Type-classified scalars.
Record-arg
A record literal placed right after a handle — @name { field - value, … } — whose fields become named parameters. It is self-closing (the matching } ends the node) and the exclusive form: it will not share a node with any other parameter source. Reach
for it when the arguments read like structured data.
@card { title - Alpha, status - draft }Both fields bind to the card's captures and render Title Alpha / Status draft. The separator can be a spaced
hyphen or a colon:
@card { title: Epsilon, status: draft }The brace binds to explicit captures (template-expansion) and to captures inferred from the body (template-implicit-captures) alike.
The record grammar is identical inline and across lines, so a many-field call can breathe — fields separate on a comma or a newline, and emphasis markers inside a value stay literal:
@reference_entry {
author - Ursula Franklin
title - The Real World of Technology
year - 1989
}The comma-in-value trap
An unquoted comma splits fields. Because the parser reads a comma as a field boundary, a value that contains one is misread as a new field with no key:
@cite { author - Boud, D., year - 2024 } ~ E_MALFORMED_RECORD
That is E_MALFORMED_RECORD — "record field missing key".
Quote the value to protect the comma, which also forces a string type:
@cite { author: "Boud, D.", year: 2024 } ~ renders: Boud, D. (2024)
~ renders: Boud, D. (2024)
(Same root cause as fixture 09-records/comma-in-value.)
Colon-scatter
In a single-line body, tokens shaped with zero whitespace are lifted out as parameters; the rest of the
line stays prose. It is the lightest way to tag a node mid-sentence:
@figure src:lamp caption:"Second-order Fresnel" figure@
src and caption become params; anything else on the line
is the figure's body.
The strict contract
Scatter fires only under an exact contract, and that strictness keeps ordinary prose from being mis-read:
- The id is
[A-Za-z][A-Za-z0-9_-]*. - The value is a bare word
[A-Za-z0-9_-]+, a"quoted"string (spaces allowed), anitalicorboldrun, or any@node(bare, parens, or with a closer). - There is no whitespace anywhere — including between the
:and the value. - The byte just before the id must be the start of the line or a non-word, non-backslash character.
@thing name:Tauraj thing@ lifts name to Tauraj; a quoted value
carries spaces — @thing mode:"complex value" thing@ (body-scatter-quoted). Multiple tokens lift while residual prose remains
(body-scatter-multi), and a repeated key is last-one-wins
(body-scatter-override).
Because the value may itself be a node or an emphasis run, scatter lifts them as sibling content: italic (body-scatter-italic-value), bold (body-scatter-bold-value), a closer-form node (body-scatter-node-value), a self-closing parens node (body-scatter-self-closing-node-value), a node carrying its own params (body-scatter-multiple-node-params), a node then a bare token (body-scatter-node-then-bare), and overriding a value with a node (body-scatter-override-with-node).
A space breaks the contract
Add a space after the colon and the token is prose again — @thing key:
value thing@ lifts nothing. A real inline node on that line stays a node; only the
scatter-lift is suppressed.
To keep a literal in prose, escape the colon — name:Tauraj suppresses the lift and the backslash is stripped from
the output (body-scatter-escape).
Form-fill body
When a node's body reads like a form — every line a key: value pair
— the body becomes the parameters. It is the most readable form
for many-field templates (citations, cards, front-matter), and the only call
form that can carry multi-line values.
@cite author: Smith year: 2024 cite@
This binds author and year; year is
type-probed to the number 2024.
What triggers form-fill
A body is read as form-fill when all three hold: it contains a
newline; its first non-blank, non-comment line matches <id>:; and it
has at least two content lines. A single content
line is not form-fill — it falls through to colon-scatter. That two-line
threshold is the rule most worth remembering.
The line grammar
Each line is <id>: <value>. One optional leading space after the
colon is trimmed, trailing whitespace is trimmed, and ~ comments and
blank lines are ignored. A line that does
not match the shape is E_MALFORMED_FORM_FIELD.
A quoted value protects commas and forces a string type. And — one surprise
worth internalising — emphasis markers in a value stay literal,
because the inline parser is bypassed: _title_ renders as the
three characters, not italic.
Multi-line values
Give a key an empty same-line value and then indent the content deeper: the block is consumed, common indentation is stripped, and lines join with newlines.
@note body: First line. Second line. note@
That binds body to First line. + newline + Second line.. Blank lines
inside the block are kept (value-multi-paragraph); a dedent ends the
value (value-block-end-by-dedent); and — a sharp edge — an indented line
that looks like key: is treated as content, not a new
field (value-indented-key-shape-is-content). An empty value with no
indented block simply moves on to the next field (value-empty-then-next-field).
The identical block grammar appears in definitions — a block record def and a value-block def both collapse to a record:
#point x: 3 y: 7 point# #point: x: 3 y: 7 !!
(fixtures 23-form-fill/block-record-def, value-block-record-def; a realistic +#bibliography value-block wrapping a form-fill with quoted
commas is bibliography-style.)
Parens
Parens — @name(p, q) — are the compact, self-closing form: the closing ) ends the node, so there is no name@ closer to write. Params are comma-separated, and colon or
space separates each key from its value. Use parens for one or two short
parameters and no body — @badge(tone good), @figure(src lamp.png, caption The lens).
@badge(tone good)
The keeper logged the lamp at dawn.The badge self-closes at ); the sentence below is its own paragraph
. The key/value separator can be a space
or a colon interchangeably — @scene(mood calm, location harbour) and @scene(mood: calm, location: harbour) bind identically
(multiple-params, colon-separator). A multi-word key joins with a
spaced hyphen — @panel(background colour - dark slate) (hyphen-multi-word-key).
- Inner whitespace is trimmed —
@x( a , b )bindsaandb(inner-whitespace). - A trailing comma is tolerated —
@x(a,)(trailing-comma). - Empty parens are legal and distinct from a bare
@xcarries none (empty-parens).
Parens carries no body of its own
If you follow a parens call with body content and a closer, a core node still attaches the params and renders the body — @aside(tone wry) the keeper said nothing aside@ works. But a template definition with no body slot has nowhere to put a
following body, so it is silently dropped. For a body, use pipes — @aside |tone wry| the keeper said nothing aside@.
A parens call must also stay on one line. Wrapping it across a
newline breaks it — the remainder, including the ), leaks into the
surrounding text (multi-line-call). A value long enough to want wrapping is a
sign to use form-fill or record-arg.
Pipes
Pipes — @name |slot| — are the only form that
composes with a body. A slot can sit on the opening line or scatter through the
body, and duplicate keys resolve last-one-wins. Reach for pipes when a node has
both parameters and content, or to switch a value partway through — otherwise
prefer record-arg or colon-scatter.
@figure |src lamp.png| |full width!| |caption The second-order lens| figure@
That passes a named src, a full width flag, and a
multi-word caption.
Pipes may appear on the opening line, or anywhere inside the body. When a key repeats, the last one wins — which makes pipes the form for switching a value mid-body:
@scene |mood calm| The harbour was still. |mood tense| Then the bell rang. scene@
Here mood ends up tense. You can also put several pipes on one line — @x |a one| |b two| |c three| x@ (multiple-pipes-per-line).
An empty || inside a body contributes no parameters (empty-pipe),
and ordinary prose that merely contains pipe characters stays literal — The
signal flag read red white red. renders as text (pipe-in-body-text).
The multi-line-value bug
A newline inside a single |…| value does not span
lines — the pipe never closes and you get E_UNCLOSED_NODE. Keep each pipe value on one line; for a genuinely multi-line
value, use a form-fill body or a record-arg brace.
The mixing rule
Two forms on one node behave differently depending on which two.
- Parens and pipes MERGE.
@card(title Alpha) |status draft| card@binds both — verified. This is not an error. - Record-arg is exclusive. Combining
{…}with parens or pipes collected before it is a hard error,E_MIXED_PARAM_SOURCE— verified for both@card |status Y| { title X }and@card(status Y) { title X }. - Record-arg then pipes drops the pipes.
@card { title: X } |status Y| card@does not merge either: the record self-closes at}, the trailing pipes are discarded, and the now-unfilled capture surfaces asE_MISSING_RECORD_FIELD— verified.
The greedy-bind hazard
A bodyless pipes call — pipes with no name@ closer
on its line — greedily swallows forward to the next matching close when nothing
separates them:
@card |title X| @card |title Y| card@
This is E_UNCLOSED_NODE: the first call keeps reading
until it finds card@, which belongs to the second. The fix is any
one of: give the call an explicit card@ closer, put a blank line after it, or place a bodyless pipes call last.
@card |title X| @card |title Y| card@
With the blank line, each renders on its own. This is why pipes is the noisiest form and why the other forms self-close.
Common mistakes
- Assuming record-arg merges with pipes — it errors, or drops them.
- Assuming parens and pipes conflict — they merge.
- Leaving a bodyless pipes call before another same-named node (greedy-bind).
- Leaving an unquoted comma inside a record-arg value — quote it.
- Adding a body when a parens template has no body slot — it is dropped; use pipes.
- Wrapping a parens call, or a single pipe value, across lines.
- Expecting a one-line body to be form-fill — it is
colon-scatter (needs a space-free
). - Forgetting that emphasis markers are literal inside form-fill values.
- An accidental
mid-prose lifting a stray param — escape or space it. - Writing
|full|expecting a flag — it is a positional value; a flag needs the trailing!.
See also
Node use & the bare reference· Type-classified scalars · Data: records · Escapes & quoting · Defining nodes · Gotchas & ambiguity