Create, edit, or plan figures in Stencila Markdown — simple image figures, executable code figures, multi-panel subfigure layouts with grid arrangements, and SVG annotation overlays using overlay components. Use when asked to add or revise a figure, chart, plot, caption, subfigure grid, panel layout, overlay annotation, callout, scale bar, arrow, region-of-interest highlight, or figure design plan for a Stencila document.
Create, edit, or plan figures in Stencila Markdown (.smd) documents. Figures wrap images or executable output with captions, auto-numbered labels, cross-references, multi-panel grid layouts, and SVG annotation overlays.
If the user asks for strategy, options, or a figure specification rather than immediate file edits, provide a plan that covers the recommended figure structure, layout, caption approach, overlay strategy, and any missing inputs. Do not imply that implementation has already happened.
Condensed references for quick lookup (try these first):
references/figure-syntax-cheatsheet.md — figure syntax, labels, layouts, padding, and overlay block structurereferences/layout-cookbook.md — copy-paste multi-panel layout patternsreferences/overlay-patterns.md — common overlay annotation recipes and anchor patternsreferences/overlay-components-quick-ref.md — condensed attribute tables for all <s:*> componentsFull documentation (large — load only when the condensed references are insufficient):
references/figures.smd — complete figures documentation (763 lines)references/figure-overlay-components.smd — full overlay component reference with demos (980 lines)references/inspect-image-tool.md — full inspect_image tool reference for coordinate inspectionreferences/lint-svg-tool.md — full lint_svg tool reference for overlay static analysisreferences/snap-tool.md — full snap tool reference for visual verification| Input | Required | Description |
|---|---|---|
Target .smd file or figure location | Required | Which document and where in it the figure should go, or which existing figure should be revised |
| Source images, existing figure content, or executable code/data | Required | The content the figure will display or the current figure material to revise |
| Caption text or intent | Required | What the caption should say or convey |
| Annotation/overlay intent | Optional | What to annotate, highlight, or label |
| Panel order and layout preference | Optional | How subfigures should be arranged |
| Calibration or scale info | Optional | Known scale for scale bars (e.g., "130px = 20 μm") |
| Orientation semantics | Optional | Axis meanings for compass indicators (e.g., N/S, A/P D/V) |
| Image dimensions | Optional | Pixel dimensions if precise overlay coordinates matter |
| Delivery mode | Optional | Whether the user wants design/specification guidance or an actual .smd edit |
When used standalone, these inputs come from the user or the agent's prompt. When used within a workflow, the workflow's stage prompt will specify how to obtain them.
| Output | Description |
|---|---|
Figure plan or updated .smd content | Either a figure specification when the user wants guidance, or the document with the new or modified figure block when the user wants implementation |
| Assumptions or questions | Any missing information that needs user confirmation |
| Verification status | Whether visual verification was performed, pending, or not applicable |
.smd edit.inspect_image to determine coordinates before writing overlay markup (see "Coordinate inspection" below).lint_svg to catch layout collisions, dangling anchor references, out-of-bounds components, and invalid attributes before visual verification (see "Overlay linting" below).snap if a Stencila server and route are available (see "Visual verification" below).This is the most important safety rule for overlay annotations:
inspect_image to determine coordinates from the actual image rather than guessing.When calibration or orientation information is missing, either ask the user or use placeholder labels that clearly indicate the values need confirmation (e.g., label="[calibrate: ? μm]").
A figure uses a colon fence with the keyword figure and optional label, #id, [layout], and {attrs}:
::: figure <label> #<id> [<layout>] {pad="<padding>"}

The caption text.
:::
Stencila auto-separates content from caption: paragraphs containing only an image (or audio/video) become content; all other paragraphs become caption. The caption can appear before or after the image.
Figures are auto-numbered ("Figure 1", "Figure 2", …). Subfigures get sub-labels (A, B, C, …). Auto-derived IDs use a fig- prefix: fig-1, fig-1a, etc. Link to a figure with [Figure 1](#fig-1) or [](#fig-1) (auto-filled). Avoid explicit labels — auto-numbering stays correct when figures are reordered.
Use #<id> on the fence line to give a figure a stable, human-readable ID that survives reordering:
::: figure #specimen-1

Photograph of the first specimen.
:::
This figure is always reachable at #specimen-1 regardless of its position. The #id can appear in any position relative to label, layout, and attributes.
Prefer stable IDs when:
snap to target a specific figure — a stable ID gives a reliable CSS selector (e.g. selector: "[id='specimen-1']") that won't break when figures are reordered.Subfigures already get automatic panel labels such as A, B, and C. Do not add <s:badge label="A">, <s:badge label="B">, etc. just to recreate those built-in labels unless the user explicitly wants additional in-image tokens. Otherwise the rendered figure can end up with duplicate panel lettering.
Wrap an executable code block inside the figure fence. The code output becomes the figure content:
::: figure
````plotly exec
{
"data": [{"type": "bar", "x": ["A","B","C"], "y": [4,7,2]}],
"layout": {"xaxis": {"title": {"text": "Category"}}}
}
````
A bar chart of values by category.
:::
This works with any execution kernel: plotly, r, python, node, etc.
Nest ::: figure blocks as subfigures. Subfigures must be indented (4 spaces) inside the parent. Add a layout in square brackets after figure:
| Pattern | Result |
|---|---|
| (none) | Subfigures stack vertically |
[row] | All subfigures in a single row |
[2] or [3] | Equal-width column grid |
[30 70] | Proportional column widths (30:70 split) |
[40 g20 40] | Two columns with a gap (g prefix) |
[a b | a c] | Layout map — a spans two rows on left |
[a a | b c] | Layout map — a spans two columns on top |
[a . | b c] | . leaves a cell empty |
[30 70 : a b | a c] | Column widths combined with layout map |
In layout maps, letters map to subfigures in order (a = first, b = second, etc.). Pipes (|) separate rows. See references/layout-cookbook.md for copy-paste patterns.
An overlay is an svg overlay fenced code block inside a figure. It renders a transparent SVG layer on top of the figure content. Use overlay components (<s:*> namespace) instead of raw SVG — they handle arrowhead definitions, label positioning, and coordinate math automatically.
Key rules:
xmlns:s="https://stencila.io/svg" on the <svg> element when using components.viewBox defines the coordinate system. Matching image dimensions is convenient (coordinates map to pixels) but any viewBox works.width/height in the chart config and use a matching viewBox.stroke, fill, and color attributes. The color shorthand sets both; explicit fill/stroke override it. Arrow markers automatically match the line's stroke color.Prefer the narrowest scope that matches the semantics of the annotation.
Prefer anchor-based positioning over raw x/y coordinates — it is more maintainable and less brittle:
<!-- Define named anchors for important features -->
<s:anchor id="peak" x="250" y="80"/>
<s:anchor id="valley" x="420" y="200"/>
<!-- Reference anchors instead of repeating coordinates -->
<s:halo at="#peak" r="15" width="8"/>
<s:callout from="#peak" dx="150" dy="-60" label="Maximum" to="#peak"/>
<s:arrow from="#peak" to="#valley" curve="quad" label="Transition"/>
Use auto-anchors for common placements: at="#s:bottom-left", at="#s:top-right", at="#s:center", etc.
Benefits: moving a feature means updating one <s:anchor>, not every component that references it.
| Component | Key attributes | Use for |
|---|---|---|
<s:arrow> | start → end, curve, tip, label | Directional lines and curves |
<s:callout> | position, label, shape, target, curve | Text labels with optional leader line |
<s:badge> | position, label | Compact pill-shaped tokens (1–4 chars) |
<s:scale-bar> | position, length, label | Calibrated measurement bars |
<s:dimension> | start → end, label, side | Engineering-style dimension lines |
<s:angle> | vertex, two ray endpoints, r, label | Angle arcs |
<s:brace> | start → end, side, label | Curly braces for grouping |
<s:bracket> | start → end, side, variant, label | Square/round brackets (e.g., p < 0.05) |
<s:roi-rect> | position, width, height, label | Rectangular ROI outlines |
<s:roi-ellipse> | center, rx, ry, label | Elliptical ROI outlines |
<s:roi-polygon> | points, label | Polygonal region outlines |
<s:spotlight> | center, r, opacity | Inverse highlight (dims outside) |
<s:marker> | position, symbol, color, label | Symbol glyphs: circle, cross, diamond, pin, plus, square, star, triangle, triangle-down |
<s:crosshair> | center, size, gap, ring, label | Reticle at a precise location |
<s:halo> | center, r, width, color, opacity | Semi-transparent glowing ring |
<s:compass> | position, size, variant, axes | Orientation indicator |
For full attribute tables, see references/overlay-components-quick-ref.md. For demos of every component, see references/figure-overlay-components.smd.
Add whitespace around content with {pad="..."} so overlays can place elements outside the image:
::: figure {pad="0 0 56 0"}
Values: 50 (all sides), "30 60" (vertical horizontal), "10 20 30 40" (top right bottom left). Quote multi-value padding.
Padding and viewBox formula. For an image W×H with pad="T R B L":
viewBox = "0 0 (W+L+R) (H+T+B)"
Image top-left is at coordinate (L, T)
All image-space coordinates shift by +L, +T
When top and left are both 0 (the common case — bottom or right padding only), image coordinates do not change. Example: {pad="0 0 56 0"} on a 600×300 image → viewBox="0 0 600 356", image coordinates unchanged. But {pad="50 20 56 20"} on the same image → viewBox="0 0 640 406", and the image top-left is now at (20, 50), so all image-space coordinates shift by +20 horizontally and +50 vertically.
::: figure

A photograph of the study site taken in September 2024.
:::
::: figure
```r exec
hist(rnorm(1000), breaks=30, col='#b0d0e8', border='#7aaccc', main='')
```
```svg overlay
<svg viewBox="0 0 600 400" xmlns:s="https://stencila.io/svg">
<s:anchor id="peak" x="335" y="100"/>
<s:halo at="#peak" r="20" width="8" color="crimson" opacity="0.4"/>
<s:callout from="#peak" dx="125" dy="-55" label="Peak near μ=0" to="#peak" curve="quad" fill="crimson"/>
</svg>
```
Distribution of 1000 random normal variates.
:::
::: figure [2]
::: figure

```svg overlay
<svg viewBox="0 0 400 250" xmlns:s="https://stencila.io/svg">
<s:anchor id="region-a" x="120" y="85"/>
<s:roi-rect from="#region-a" dx="-70" dy="-45" width="140" height="90" label="Region A" stroke-style="dashed"/>
</svg>
```
Left panel.
:::
::: figure

```svg overlay
<svg viewBox="0 0 400 250" xmlns:s="https://stencila.io/svg">
<s:roi-ellipse cx="200" cy="125" rx="80" ry="50" label="Region B"/>
</svg>
```
Right panel.
:::
Two panels with individual overlay annotations.
:::
Use inspect_image to determine precise coordinates for overlay annotations instead of guessing. This is faster and more accurate than estimating coordinates and iterating with snap.
Typical workflow:
inspect_image(file_path: "figure.png", grid: {x_divisions: 10, y_divisions: 10})
inspect_image(file_path: "figure.png", crop: {x: 80, y: 100, width: 140, height: 100}, grid: {x_divisions: 5, y_divisions: 5})
inspect_image(file_path: "figure.png", probes: [{id: "tip", x: 112, y: 160}, {id: "base", x: 185, y: 118}])
With padding: When the figure uses {pad="..."}, pass the same padding to inspect_image so coordinates match the overlay's viewBox:
inspect_image(file_path: "figure.png", coordinate_space: {pad: {top: 0, right: 220, bottom: 0, left: 0}}, grid: {x_divisions: 10, y_divisions: 10})
For full details, see references/inspect-image-tool.md.
After authoring or editing an overlay, run lint_svg to catch common errors before visual verification. Pass the SVG overlay source string to the tool:
lint_svg(svg_content: "<svg viewBox=\"0 0 600 400\" xmlns:s=\"https://stencila.io/svg\">...</svg>")
The linter checks for:
viewBoxfrom="#missing" pointing to undefined anchors<s:anchor> defined but never referencedxmlns:s on the <svg> elementcurve="wobbly")Fix any errors and warnings before proceeding to visual verification with snap. To suppress a collision warning for an intentional overlap, place <!-- lint-ignore collision --> before the component.
If a Stencila server and rendered route are available, use snap to verify the final rendered result after authoring overlays:
snap(route: "/docs/", screenshot: true, selector: "stencila-figure")
When a figure has a stable #id, use it for a more targeted snap that captures exactly that figure:
snap(route: "/docs/", screenshot: true, selector: "[id='specimen-1']")
Prefer the rendered directory route when the source file is index.*, main.*, or README.*; for example, docs/README.md, docs/main.md, and docs/index.md all render at "/docs/".
Use inspect_image for coordinate determination and snap for final rendered verification — they serve complementary roles. inspect_image operates directly on image files without a server, while snap captures the fully rendered document including overlays, layout, and theme.
If snap is unavailable, mark visual verification as pending. Do not claim rendered correctness unless snap was actually run.
For details on snap parameters and usage patterns, see references/snap-tool.md.
::: figure blocks must be indented 4 spaces inside the parent. Missing indentation makes them sibling figures, not subfigures.width and height in the chart config and match the overlay viewBox. Dynamic resizing can misalign overlays.viewBox can use any coordinate system — matching image pixel dimensions is convenient but not required. All annotation geometry must use the same coordinate space as the viewBox.pad value when using more than one number: {pad="30 60"}, not {pad=30 60}.fig- prefix with the label lowercased — fig-1, fig-2a. Use #<id> on the fence line for a stable ID that survives reordering (e.g. ::: figure #specimen-1).xmlns:s="https://stencila.io/svg" on the <svg> element causes components to be treated as unknown elements and silently ignored.