Tutorial

From nothing to a themed diagram, one step at a time

This page is meant to be followed along with. Every diagram below is rendered by prism while this page is built — nothing here is a screenshot, and if any step stopped working the site would fail to build.

Step 0 — install

pip install prism-svg

The import name is prism; the distribution is prism-svg because prism was already taken on PyPI. Check it worked:

prism archetypes

You should see ten names. If you have uv, uvx --from prism-svg prism archetypes works without installing anything.

Step 1 — your first diagram

A prism spec is YAML. It needs exactly one thing: a type, naming the archetype. Here is the smallest useful diagram there is.

import prism

spec = """
type: flow
steps:
  - label: Draft
  - label: Review
  - label: Ship
"""

prism.diagram(spec)

That is the whole loop: write YAML, get SVG. You never positioned anything — prism chose the box sizes, the spacing, the arrow geometry and the colours.

Save that YAML to first.yaml and you can do the same from a terminal:

prism render first.yaml -o first.svg

Step 2 — give the nodes some substance

Every archetype in prism consumes the same node shape. Learn it once and it works in all ten. The useful fields are sublabel, icon and accent.

spec = """
type: flow
steps:
  - label: Draft
    sublabel: outline + notes
    icon: pencil
    accent: 0
  - label: Review
    sublabel: two readers
    icon: eye
    accent: 1
  - label: Ship
    sublabel: to production
    icon: rocket
    accent: 2
"""

prism.diagram(spec)

accent is an index into the theme’s colour ramp, not a colour. That indirection is the point: the theme owns the palette, so the same spec restyles cleanly. There are 1756 icons available (prism icons), all from Lucide.

Step 3 — title, subtitle, caption

These live at the top level of the spec, alongside type, because they belong to the diagram rather than to any node.

spec = """
type: flow
title: Editorial process
subtitle: for long-form pieces
caption: "Source: the house style guide"
steps:
  - {label: Draft, icon: pencil, accent: 0}
  - {label: Review, icon: eye, accent: 1}
  - {label: Ship, icon: rocket, accent: 2}
"""

prism.diagram(spec)

That inline {label: Draft, icon: pencil} form is ordinary YAML — use whichever style you find more readable.

Step 4 — change the direction

Most archetypes take a small number of structural options. flow takes direction.

spec = """
type: flow
direction: down
steps:
  - {label: Draft, icon: pencil, accent: 0}
  - {label: Review, icon: eye, accent: 1}
  - {label: Ship, icon: rocket, accent: 2}
"""

prism.diagram(spec)

Step 5 — pick the archetype by the shape of the meaning

This is the one judgement prism asks of you. Not what is this about, but what shape is the idea. The same three-stage editorial process, told as a loop rather than a line, wants cycle:

spec = """
type: cycle
title: Editorial loop
center: {label: A piece}
steps:
  - {label: Draft, icon: pencil, accent: 0}
  - {label: Review, icon: eye, accent: 1}
  - {label: Revise, icon: wrench, accent: 2}
"""

prism.diagram(spec)

And the same content as a judgement about two independent dimensions wants quadrant, where items carry coordinates in 0..1:

spec = """
type: quadrant
title: What to write next
axes:
  x: {label: Effort, low: Low, high: High}
  y: {label: Reach, low: Narrow, high: Wide}
quadrant_labels: [Quick posts, Flagship, Notes, Slog]
items:
  - {label: Weekly note, x: 0.15, y: 0.3}
  - {label: Field guide, x: 0.8, y: 0.85}
  - {label: Talk writeup, x: 0.45, y: 0.6}
"""

prism.diagram(spec)

The catalog shows all ten with the spec that produced each.

Step 6 — hierarchies, the one recursive case

hierarchy is the only archetype whose data nests, through children. That is data recursion, not template composition — you still cannot put one archetype inside another.

spec = """
type: hierarchy
title: A book
root:
  label: Manuscript
  icon: book
  children:
    - label: Part I
      children:
        - {label: Chapter 1}
        - {label: Chapter 2}
    - label: Part II
      children:
        - {label: Chapter 3}
"""

prism.diagram(spec)

Step 7 — switch the theme

Add one line. Nothing else about the spec changes.

spec = """
type: flow
theme: dark
title: Editorial process
steps:
  - {label: Draft, icon: pencil, accent: 0}
  - {label: Review, icon: eye, accent: 1}
  - {label: Ship, icon: rocket, accent: 2}
"""

prism.diagram(spec)

Four themes ship: default, dark, paper and mono. paper is the one to reach for in print — serif typography, warm surface, hairline strokes:

prism.diagram(spec.replace("theme: dark", "theme: paper"))

Step 8 — nudge a single token

You rarely need a whole new theme to change one thing. tokens: overrides individual theme values by dotted path.

spec = """
type: flow
theme: paper
tokens:
  geometry.radius: 0
  geometry.gap: 44
title: Wider gaps, square corners
steps:
  - {label: Draft, icon: pencil, accent: 0}
  - {label: Review, icon: eye, accent: 1}
  - {label: Ship, icon: rocket, accent: 2}
"""

prism.diagram(spec)

A mistyped path fails loudly rather than being ignored — try geometry.radiuss and prism will tell you what it expected.

Step 9 — your own theme

For a whole document set you want a theme file of your own. Scaffold one:

prism new-theme house

That writes house.yaml here, copied from the default theme and already carrying its new name. Start from whichever bundled theme is closest:

prism new-theme house --from paper      # start from the print theme
prism new-theme house -o themes/house.yaml

The file opens with a comment explaining the two rules you have to respect, so you do not have to remember them. Then point any spec at it:

type: flow
theme: ./house.yaml
steps:
  - {label: Draft}

Edit the tokens and every diagram that references the file restyles at once.

The two rules, since they surprise people: typography.family must be grotesque, serif or mono, and typography.weight values must be 400 or 700. Those are exactly the fonts and weights whose metrics prism has vendored. Anything else would mean measuring text against a font your reader does not have, and labels would overflow their boxes. prism new-theme validates the file it writes, so a bad edit fails when you next render rather than producing a quietly broken picture.

Step 10 — when something is wrong

prism’s errors are written to be acted on. Each names the bad value and points at the valid neighbours.

prism.render_str("""
type: flow
steps:
  - {label: Draft, icon: pencl}
""")
---------------------------------------------------------------------------
UnknownIcon                               Traceback (most recent call last)
Cell In[11], line 1
----> 1 prism.render_str("""
      2 type: flow
      3 steps:
      4   - {label: Draft, icon: pencl}

File ~/work/prism/prism/prism/__init__.py:89, in render_str(source)
     87 saved, _TGroup.stack = _TGroup.stack, []
     88 try:
---> 89     canvas = frame(archetype.build(spec, ctx), envelope, ctx)
     90     svg = canvas._repr_svg_()
     91 finally:

File ~/work/prism/prism/prism/archetypes/flow/build.py:25, in FlowArchetype.build(self, spec, ctx)
     23 def build(self, spec: FlowSpec, ctx: RenderContext) -> Group:
     24     theme = ctx.theme
---> 25     boxes = [build_node_box(step, ctx) for step in spec.steps]
     27     if spec.direction == "right":
     28         spine = RowLayout(boxes, align="middle", gap=theme.geometry.gap)

File ~/work/prism/prism/prism/nodebox.py:109, in build_node_box(node, ctx, max_width)
    106 body: Group = ColumnLayout(stack, align="middle", gap=theme.geometry.gap / 6)
    108 if node.icon:
--> 109     glyph = build_icon(
    110         node.icon,
    111         size=theme.size("label") * 1.4,
    112         color=ink,
    113         stroke=theme.geometry.stroke,
    114     )
    115     body = RowLayout([glyph, body], align="middle", gap=theme.geometry.pad / 2)
    117 badge = build_badge(node, ctx)

File ~/work/prism/prism/prism/icons.py:68, in build_icon(name, size, color, stroke)
     66 def build_icon(name: str, size: float, color: Color, stroke: float) -> IconShape:
     67     """Return the named icon as a shape, scaled to `size` and centred on origin."""
---> 68     return IconShape(name, size, color, stroke)

File ~/work/prism/prism/prism/icons.py:44, in IconShape.__init__(self, name, size, color, stroke)
     42 data = _icons()
     43 if name not in data:
---> 44     raise UnknownIcon(name, data)
     45 super().__init__(fill=Colors.Transparent, stroke=color, width=stroke)
     46 self.name = name

UnknownIcon: unknown icon 'pencl' — did you mean: pencil, pen, pencil-off?

The same holds for unknown archetypes, unknown theme tokens, and dangling references such as a matrix cell naming a row that does not exist.

Step 11 — put it in your own Quarto document

Everything above is a real Quarto page. The pattern is:

```{python}
#| label: fig-process
#| fig-cap: "The editorial process"
#| echo: false
import prism
prism.diagram("figures/process.yaml")
```

prism.diagram() accepts a path or a spec string and returns an object that knows how to display itself, so Quarto embeds the SVG directly — no intermediate file and no rasterisation. See In Quarto for PDF output and batch rendering.

Where to go next

  • The catalog — all ten archetypes and their fields.
  • Themes — every archetype in every theme, side by side.
  • For agents — if you are wiring prism up to a language model.

And when you want a field prism does not have, prism schema <archetype> is the authoritative answer on what a given archetype accepts.