4  Extending

zx.theme is ordinary data, so a project theme extends it exactly the way typograph’s own Theming chapter describes: shallow, explicit composition with +, never by mutating this package’s values.

#import "@preview/typograph:0.1.0" as typ
#import "@preview/typograph-zx:0.1.0" as zx

#let pentagon = typ.node-type("pentagon")

#let node-presets = zx.theme.node-presets + (
  z: zx.theme.node-presets.z + (
    fill: rgb("#c7e9ff"),
    stroke: 0.8pt + navy,
  ),
  pentagon: (
    shape: typ.shapes.regular(vertices: 5, rotate: -90deg),
    fill: rgb("#f2e8ff"),
    stroke: 0.8pt + purple,
    min-size: 13pt,
    inset: 3pt,
  ),
)

#let edge-presets = zx.theme.edge-presets + (
  ghost: (stroke: (paint: gray, dash: "dashed")),
)
#let ghost = typ.edge-type("ghost")

#let my-theme = typ.theme(
  palette: zx.theme.palette,
  node-presets: node-presets,
  edge-defaults: zx.theme.edge-defaults,
  edge-presets: edge-presets,
)
#import "@preview/typograph:0.1.0" as typ
#let diagram = typ.diagram.with(theme: my-theme)

#diagram(scale: 1.1cm, {
  let p = pentagon(0, 0, label: [P])
  let q = zx.z(1.4, 0, label: $alpha$)
  ghost(p, q)
})

A restyled z, a new pentagon kind, and a themed ghost edge

classic.node-presets.z + (fill: ..) keeps z’s existing shape, shape-labelled, min-size, and inset and only overrides fill/ stroke. Writing z: (fill: blue) instead would replace the whole preset, discarding those other fields — a theme never guesses which fields you meant to keep.

pentagon and ghost are brand new kinds, declared with the exact same node-type()/edge-type() factories z/plain themselves are built from — see Constructors. There is no separate mechanism for “this theme’s own kinds” versus “a project’s added kinds.”