Text, icons & images

Text is content, not a shape — a bare string is a text node wherever it stands. Icons are Phosphor glyphs drawn as paths. Images embed.

A string is a node

{ direction: column; gap: 14; align: start; }

"Plain text stands on its own."
"Styled in place." { color: --rose-ink; font-weight: bold; }
"Two lines,\nwith a break." { font-style: italic; }
|box#card| { fill: --purple-wash; stroke: --purple-deep; } [
  "A heading" { font-weight: semibold; font-size: 17; color: --purple-ink; }
  "and a line of body text under it." { color: --purple-ink; }
]
Plain text stands on its own. Styled in place. Two lines,with a break. A heading and a line of body text under it.

Where the string stands is what it becomes:

  • On its own — a free text node.
  • Inside a shape’s [ ] — that shape’s text. Several strings are several nodes, stacked, because a shape’s content flows as a column.

Either way it takes a style block of text properties — color, the font-* family, letter-spacing, text-decoration, translate, rotate — and wears classes like anything else.

Anything that needs a border, padding or an id wraps in a |block|: the frameless box.

Wrapping

{
  gap: 20; align: start;
  |box| { max-width: 170; fill: --teal-wash; stroke: --teal-deep; color: --teal-ink; }
}

|box#a| "Ship the release notes to every customer who asked"
|box#b| "Rotate the signing keys before the audit window opens" { align: start; }
|box#c| "Archive dashboards nobody opened this quarter" { align: end; }
Ship the releasenotes to everycustomer whoasked Rotate the signingkeys before theaudit windowopens Archivedashboardsnobody openedthis quarter

max-width caps a box’s automatic width, and the text wraps to honour it, breaking at whitespace first. The wrapped size is the measured size, so those three cards sit at their true heights rather than at a guess.

There is no text-align. Lines align by the box’s own packing knob — align in a column box, justify in a row — which is why b and c above differ without either one naming text.

Icons

{
  gap: 26;
  .tag { fill: --sky-wash; stroke: --sky-ink; color: --sky-ink; }
}

|icon| "bell"
|icon| "heart" { fill: --rose-wash; stroke: --rose-ink; }
|icon| "cloud" { fill: none; stroke: --sky-deep; }
|icon| "chat-circle" .tag [ "3" ]
|sign#docs| "folder-simple" .tag [ "Docs" ]
3 Docs

|icon| draws a Phosphor icon named by its label — or by symbol: — as inline paths. No icon font, and only the glyphs a figure actually uses are embedded.

It paints like a box:

  • fill is the soft body, stroke the line.
  • fill: none makes it a single-tone line icon, as cloud is above.
  • It is a square that grows with its [ ] text.

|sign| is the larger preset with room for a word, and it takes links like any other node.

Icons make cards

{
  layout: grid; columns: repeat(3); gap: 20;
  |card::box| { padding: 16 20; }
  |stat::block| { font-size: 22; font-weight: bold; }
  |cap::block| { font-size: 12; color: --muted; letter-spacing: 1; }
}

|card| { fill: --rose-wash; stroke: --rose-deep; } [
  |icon| "bell" { fill: --rose-wash; stroke: --rose-ink; }
  |stat| "128" { color: --rose-ink; }
  |cap| "ALERTS"
]
|card| { fill: --purple-wash; stroke: --purple-deep; } [
  |badge| "!"
  |icon| "lock" { fill: --purple-wash; stroke: --purple-ink; }
  |stat| "24" { color: --purple-ink; }
  |cap| "KEYS"
]
|card| { fill: --lime-wash; stroke: --lime-deep; } [
  |icon| "users" { fill: --lime-wash; stroke: --lime-ink; }
  |stat| "1.4k" { color: --lime-ink; }
  |cap| "TEAM"
]
128 ALERTS 24 KEYS ! 1.4k TEAM

Nothing here says direction. A shape’s children stack as a column, so an icon over a number over a caption is just three children in order.

The rest is the language you already have — a define per role, a class-free { } where one card differs, and a |badge| pinned to the middle card, which is the same overlay the diagrams page pinned to a card.

That icon is one node type everywhere: the same |icon| is a sequence participant’s glyph, or a tree topic’s decoration.

Images

|image#logo| { src: "assets/logo.svg"; width: 120; height: 60; }
|image#photo| { src: "https://example.com/site.png"; width: 320; height: 180; fit: cover; }

|image| needs src, width and height. What happens next depends on where the source lives:

  • A local path is read once and embedded, so the output stays self-contained — an SVG asset as a nested, id-isolated <svg>, a raster as a base64 data URI.
  • A URL passes through untouched.

fit: maps the picture into its box: auto letterboxes, and contain, cover and stretch do what CSS says.

Go deeper

Statements & the label covers text content, paint, stroke & text the text family, and nodes icons and images.