pdg XML → PDF

The pdg book · Chapter 05 of 10 · The flexbox of pdg

Layout — vertical & horizontal

<vertical> and <horizontal> are pdg's flexbox — gap, main/cross alignment, and flex weights. This is how you build any arrangement on the page.

This is the chapter that unlocks the rest. Almost every layout you will ever build is a nesting of two containers: <vertical> stacks its children top to bottom, <horizontal> places them left to right. They are pdg's flexbox, and they behave the way display: flex trained you to expect.

Stacking with gap

<vertical> is flex-direction: column. The gap attribute is exactly CSS gap — space between children, with no leading or trailing gap.

Try it — edit the source Open in Playground
<vertical gap="10px">
  <text size="16" bold="true">First</text>
  <text size="13">Second, ten points below the first.</text>
  <text size="13">Third, ten points below the second.</text>
</vertical>

<horizontal> is flex-direction: row:

Try it — edit the source Open in Playground
<horizontal gap="12px">
  <rect width="50px" height="50px" fill="#c4341f" />
  <rect width="50px" height="50px" fill="#1b1a17" />
  <rect width="50px" height="50px" fill="#9a2615" />
</horizontal>

main and cross alignment

Two attributes position children, and they map straight onto flexbox:

  • main is alignment along the stacking axis — CSS justify-content. Values: start, center, end, space-between, space-around, space-evenly.
  • cross is alignment across it — CSS align-items. Values: start, center, end, stretch.

A row that pushes its ends apart and centres them vertically — the classic header bar:

Try it — edit the source Open in Playground
<horizontal main="space-between" cross="center"
            style:padding="12px" style:background="#1b1a17">
  <text size="16" bold="true" color="#faf6ed">Acme Co.</text>
  <text size="12" color="#9a948a">Invoice #0042</text>
</horizontal>

cross="center" is why the small date sits on the same midline as the big title, even though they are different heights — align-items: center to the letter.

flex: sharing leftover space

There is no width: 50% in pdg. Instead, give children a flex weight and they share the leftover space along the main axis in proportion — precisely CSS flex-grow. Two equal columns:

Try it — edit the source Open in Playground
<horizontal gap="12px">
  <text flex="1" size="12" style:padding="10px" style:background="#ece5d5">
    I take one share of the width.
  </text>
  <text flex="1" size="12" style:padding="10px" style:background="#faf6ed">
    I take the other. Together we fill the row.
  </text>
</horizontal>

Change one to flex="2" and it grows to twice the width of the other. A fixed sidebar beside a flexible main column is just a fixed width next to a flex:

Try it — edit the source Open in Playground
<horizontal gap="12px">
  <rect width="60px" height="80px" fill="#c4341f" />
  <text flex="1" size="12" style:padding="10px" style:background="#faf6ed">
    The red bar is a fixed 60px. I am flex="1", so I absorb everything that's
    left — resize the page and only I change width.
  </text>
</horizontal>

stretch: filling the cross axis

cross="stretch" makes children fill the container's width (in a <vertical>) or height (in a <horizontal>) — like align-items: stretch. Here three banners stretch to a common width instead of each shrinking to its own text:

Try it — edit the source Open in Playground
<vertical gap="6px" cross="stretch">
  <text size="13" style:padding="8px" style:background="#1b1a17" color="#fff">One</text>
  <text size="13" style:padding="8px" style:background="#c4341f" color="#fff">Two</text>
  <text size="13" style:padding="8px" style:background="#9a2615" color="#fff">Three</text>
</vertical>

Wrapping rows

When a <horizontal> holds more rigid children than fit across the width, it wraps them onto a new line instead of overflowing — like flexbox with flex-wrap: wrap. The gap applies both between items on a line and between the wrapped lines.

Try it — edit the source Open in Playground
<horizontal gap="8px">
  <rect width="70px" height="30px" fill="#c4341f" />
  <rect width="70px" height="30px" fill="#1b1a17" />
  <rect width="70px" height="30px" fill="#9a2615" />
  <rect width="70px" height="30px" fill="#447ac4" />
  <rect width="70px" height="30px" fill="#1a7f3c" />
</horizontal>

Wrapping is for rows of rigid items. The moment any child has a flex weight, the row commits to a single line and shares the width instead — a flex child is asking to fill the row, not to wrap off the end of it.

Nesting is the whole game

Real layouts are containers inside containers. A vertical page that holds a horizontal header, then a two-column body:

Try it — edit the source Open in Playground
<vertical gap="14px">
  <horizontal main="space-between" cross="center">
    <text size="18" bold="true">Report</text>
    <text size="11" color="#847d6c">June 2026</text>
  </horizontal>
  <line stroke="#ddd5c4" />
  <horizontal gap="14px">
    <text flex="2" size="12">The wide main column carries the body copy and
      takes two shares of the width.</text>
    <text flex="1" size="11" style:padding="10px" style:background="#faf6ed">
      A narrow aside, one share wide.</text>
  </horizontal>
</vertical>

That nesting — verticals holding horizontals holding verticals — is how you build any page. If you want the precise mechanics of how space is offered and split, the layout protocol spells it out, but the flexbox intuition above will carry you a long way.

Two more layout tools — overlapping content and springs — are in the next chapter.