Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration And Renderer Callbacks

deixis-configure and deixis-set are identical aliases. The optional short API uses configure and names the scoped container note-block, preserving native block.

Start with direct options on a note. Use deixis-set for defaults shared by subsequent declarations, and put it inside a deixis-block to keep it local:

Source

#deixis-set(stroke: blue)
A point#deixis-footnote[An explanation.].

#deixis-block[
  #deixis-set(stroke: orange)
  A local point#deixis-footnote[A locally styled explanation.].
]

Complete source and preview setup

Output · Click to zoom

A small function can serve as a reusable preset; there is no additional preset registry. See the editable example.

For a final copy without editorial annotations, use deixis-set(omit: (series: ("todo",))). This creation policy preserves passages and skips the selected annotations entirely; omit: none includes all series. See review comments and final builds.

Settings follow Deixis block scopes. Ordinary Typst content brackets do not restore them. Leaving a deixis-block restores its parent’s settings. A top-level auto argument leaves the current setting unchanged; it does not reset a value to its factory default. Use none only where that option supports it (for example, disabling a stroke). Empty dictionaries have the rules described below.

Type And Component Dictionaries

Shared visual values such as stroke/fill/radius can select a note type in deixis-set, then a component within that type:

Fragment · Requires surrounding code

#deixis-set(
  stroke: (inline-mark: blue, inset-note: (body: teal, connector: gray), rest: red),
  radius: (mark: 2pt, body: 0pt),
)

mark, body, and connector name components; connector is the line drawn between them. nodes supplies both mark and body; an explicit component takes precedence over nodes. rest supplies unmatched types/components at its level. Keep note-type and component keys at different levels, including when using nodes. Local note options accept component scope; note-type scoping belongs in deixis-set.

Type keys select the component’s actual adapter. An inset note with an inline mark uses inline-mark for its mark and inset-note for its body/connector. Region and phantom originals have their own mark types. To style both halves of just one note family, a small wrapper preset with local component options is usually clearer than document-wide mark defaults.

Numbering is the exception: it identifies the note, so a wrapper’s family key applies to the mark it creates. At each level, the family key precedes the mark type, which precedes rest:

Fragment · Requires surrounding code

#deixis-set(numbering: (footnote: "*", endnote: "i", inline-mark: "a", rest: "1"))

Footnotes use symbols and endnotes roman numerals; margin, inset and inline notes with inline marks use letters; their region and phantom marks use "1". A standalone deixis-mark has no family and uses its mark type. A local numbering: or an inner deixis-set level still takes precedence.

Single-role options choose the relevant component automatically:

OptionsComponent
numbering, marker-position, inline-mode, region-shapemark
Body styling, backlinks, body/group spacing, renderers, containers, margin placement policybody (also accepts nodes)
connectorconnector
marker-style, shared stroke/fill/radius/layerThe component being rendered

For example, render-single: (body: my-renderer) and container-func: (nodes: my-container) work without returning an unresolved dictionary to the painter. The simpler render-single: my-renderer also works. Sidenotes use the margin-note type; see the preset details below.

Native Values And Incremental Updates

A dictionary containing native value keys stays a value. In particular, radius: (top-left: 3pt, bottom-right: 6pt) supplies native corner values. Inline box/parentheses end caps retain those outer corners and square their inner edges. radius: (:) explicitly requests square corners, including in inline marks.

Rectangular regions, body containers, and inline box/parentheses/underline marks also accept native per-edge strokes:

Source

#deixis-inset-note-body(layer: "flow", container-func: deixis-rect-container,
  stroke: (left: blue + 2pt, bottom: gray, rest: none),
)[An explanation with two borders.]

Complete source and preview setup

Output · Click to zoom

top, right, bottom, left, x, y, and rest follow native rectangle semantics. Missing edges are absent; an explicit stroke: (:) suppresses all borders. Each edge accepts a stroke/color/length or none, with a default 1pt thickness for color-only strokes. A stroke-attribute dictionary such as (paint: blue, thickness: 2pt) remains a single stroke. Region radius top/bottom/left/right/rest shorthands also survive page fragmentation; only adjoining edges and corners are removed at page joins.

For connectors, select a single link stroke and radius independently of the rectangular node styles:

Fragment · Requires surrounding code

#deixis-set(
  stroke: (nodes: (left: blue, rest: none), connector: gray + 0.7pt),
  radius: (nodes: (top: 4pt, bottom: 6pt), connector: 2pt),
)

Enabled connectors diagnose an edge/corner dictionary used as a line stroke or turn radius; connector.stroke/connector.radius can also supply the scalar override. inline-mode: "text-fill" needs a single stroke paint; stroke: none or a colorless stroke such as 1pt preserves the passage’s inherited color. Edge maps are ambiguous for text paint and receive a focused diagnostic. Custom containers/shapes own the native styles they accept; per-edge borders apply to rectangles, while ellipses require a single stroke. See the small native-style example.

Compatible dictionaries merge recursively across repeated settings updates, so separate component/type branches and native corner updates can be incremental. Changing representation replaces the old dictionary at that level:

Fragment · Requires surrounding code

#deixis-set(radius: (body: 3pt))
#deixis-set(radius: (top-left: 6pt)) // Replaces the component map.
#deixis-set(radius: (:)) // Clears it to an empty native radius.

The same rule prevents type scopes and component scopes from being combined by separate updates. An empty dictionary clears its current level. For options without literal dictionary values, an empty scope map has no matching branch and falls through the normal cascade. auto on a local component override keeps the inherited value; auto within a settings map clears that branch so lower configuration levels can supply it.

Connector dictionaries and head specifications keep their own meanings. For example, connector: (heads: (mark: "circle", body: "triangle")) selects endpoints, and connector: (heads: (:),) explicitly clears heads instead of inheriting earlier defaults. Margin mark-align: (mark: bottom, body: top) likewise describes an anchor pair; it stays intact rather than selecting the body’s branch. These are distinct from component-scoped visual styles. See connectors. container-args and annotation data remain opaque explicit dictionaries outside this cascade.

Declaration-Time Defaults

The original mark captures its marker style when it is declared; references and reused marks retain that style. A body resolves its own marker-style branch, renderer and layout defaults where its body declaration occurs. This also holds when the body precedes its original. Changing defaults between declarations does not retroactively restyle earlier components. An inline body follows the same body-marker cascade as footnote, endnote, margin and inset bodies:

Source

#deixis-set(marker-style: (mark: it => super(it), body: it => strong(it)))
#deixis-mark(id: <detail>)[A passage]
#deixis-inline-note-body(of: <detail>)[An explanation with a bold marker.]

Complete source and preview setup

Output · Click to zoom

Explicit local body options take precedence. Shared stroke/fill/radius also inherit explicit options from the original beneath the body’s own overrides; the original’s resolved marker callback is reserved for mark reuse. Collected endnotes retain their body defaults until printed, unless the printer explicitly overrides them.

Independent connections have their own route defaults: straight outside deixis-routing, sharp orthogonal inside it. Configure them with connector: (connection: (route: "curve")) in deixis-set, or a local connector.route. A margin-note/inset-note connector default configures note shorthand and does not replace that independent default. See routing policies.

Rendering Extension Points

Use a container for a simple border/background around the built-in output. Use a single-note renderer to inspect annotation data or replace marker/body layout:

Source

#let my-renderer(note, ..args) = {
  // note.id, note.data, note.styles, and note.container-args are available.
  deixis-native-render-single(note, ..args.named())
}
#deixis-set(render-single: my-renderer)
A point#deixis-footnote[Rendered through the delegated renderer.]

Complete source and preview setup

Output · Click to zoom

Renderer callbacks should accept ..args: adapters can supply inner-width, group gap, or aligned marker metrics. Forward named arguments when delegating. Keep live annotation content in the supplied record. Use deixis-generate-body-marker(note) (or deixis-generate-body-meta) for the single native destination, and deixis-generate-body-content(note) for live start/end locations. See custom renderers for the complete recipe and partial-output behavior. Renderers may be called for measurement; live content should not be independently executed again inside a callback.

Normal inline bodies use the same shared renderer entry as other adapters and include the selected render-single in their record, including standalone bodies. No unnecessary geometry arguments are added when there is no width to constrain. Single/group renderers, body and marker styles, and region shapes accept functions. Containers also accept the five preset names. These values are validated after scope selection, with the parameter named on invalid input. Ordinary style validation happens when a declaration selects the value; unused type branches are not eagerly resolved. Palette validation is eager because it defines a creation policy rather than a component style.

Endnote printer single/group renderer and body-style overrides accept auto or a function and validate before collecting bodies. Its container-func override also accepts a preset name. Its marker-style override also retains component selection and validates the resolved callback. Omitting printer overrides preserves the callbacks captured by each body.

The default endnote grid paints cells directly for default aligned single renderers. If a series contains another single renderer, it stacks that series natively and invokes its single renderers. A container override alone keeps the grid path; choose deixis-native-render-group or deixis-aligned-render-group to invoke containers with the default single renderer. Inline-decoration bodies (inline: true) also retain their native formatting path. Body containers, custom renderer forwarding, and these bypass rules are described in containers.

Sidenote Presets

deixis-sidenote and deixis-sidenote-body are margin presets with side: "outside" and mark-align-strictness: "loose". They share margin packing, counters, styles, body records, and query type. Configure them with margin-note: (...); there is no separate sidenote type. The old unused sidenote: configuration key now produces a migration diagnostic instead of silently selecting nothing. Type filters continue to use "margin-note".

Preset values behave like native function .with(...) defaults: an explicit call overrides them. The preset’s "loose" value takes precedence over global alignment strictness; pass mark-align-strictness: auto to inherit settings deliberately:

Source

#deixis-set(mark-align-strictness: (margin-note: "strict"))
#deixis-sidenote(mark-align-strictness: auto)[Inherit strict alignment.]

Complete source and preview setup

Output · Click to zoom

For a separately styled family, define a small function preset with explicit options and use annotation data/series when a custom catalogue should distinguish it. This keeps presentation presets separate from annotation identity and layout engine types.

For factory appearance and a consistent accent across components, see appearance and color and the annotation-colors recipe.

palette is a dedicated creation policy: a named palette ("deixis", "okabe-ito", "tol-bright", "tol-muted", or "tableau") or a nonempty color array starts a new cycle, none disables it, and auto leaves it unchanged. Unlike style values, it does not take type/component dictionaries. See automatic palettes for scope restoration, forward/reused identities and explicit-style precedence.