Layouts
GlyphJS renders blocks in a single vertical flow by default. The layout components let you break out of that flow and arrange blocks side by side, stacked in sections, or wrapped in styled containers.
There are three layout primitives:
| Component | What it does |
|---|---|
ui:columns | Splits horizontally — two or more blocks side by side |
ui:rows | Splits vertically — two or more blocks stacked with gap control |
ui:panel | Wraps one block in a styled container (card, bordered, elevated, ghost) |
All three work the same way: children are suppressed block variables defined with =_name, then referenced by name in the layout block’s children (or child) field.
The suppressed variable pattern
The key idea is to define blocks without rendering them, then hand them to a layout component. The =_name suffix on a ui: block suppresses it from the document flow:
```ui:callout=_myBlock ← compiled and stored as "myBlock", NOT rendered heretype: tipcontent: Hello.children: [myBlock]The suppressed block must appear **before** the layout block that references it.
---
## ui:columns — horizontal split
`ratio` controls relative widths. `[2, 1]` gives a two-thirds / one-third split:
export const colBasic = `\`\`\`ui:callout=_statustype: infotitle: System Statuscontent: All services nominal. No incidents in the past 30 days.\`\`\`
\`\`\`ui:kpi=_kpismetrics: - label: Uptime value: "99.97%" trend: up sentiment: positive - label: P99 Latency value: 148ms trend: flat sentiment: neutralcolumns: 2\`\`\`
\`\`\`ui:columnsratio: [2, 1]gap: 1.5remchildren: [status, kpis]\`\`\``;
<GlyphPreview client:only="react" source={colBasic} />
Omit `ratio` for equal widths. `gap` accepts any CSS length string.
---
## ui:rows — vertical split
Used standalone, `ui:rows` stacks blocks with consistent spacing:
export const rowsBasic = `\`\`\`ui:callout=_p1type: infotitle: Phase 1 — Discoverycontent: User research and problem framing. Due March 14.\`\`\`
\`\`\`ui:callout=_p2type: tiptitle: Phase 2 — Designcontent: Wireframes, prototypes, and review. Due March 28.\`\`\`
\`\`\`ui:callout=_p3type: warningtitle: Phase 3 — Buildcontent: Engineering sprint begins April 1.\`\`\`
\`\`\`ui:rowsgap: 0.75remchildren: [p1, p2, p3]\`\`\``;
<GlyphPreview client:only="react" source={rowsBasic} />
---
## Recursive layouts
Nest `ui:rows` inside a column cell (or `ui:columns` inside a row cell) to build 2D layouts. Define the inner layout as a suppressed variable, then reference it from the outer layout.
### Classic split — wide left, narrow right with two rows+----------+----------+ | | top | | left +----------+ | | bottom | +----------+----------+
export const classicSplit = `\`\`\`ui:kpi=_pipelinetitle: Q1 Pipelinemetrics: - label: Deals Open value: "84" trend: up sentiment: positive - label: Win Rate value: "34%" trend: flat sentiment: neutral - label: Forecast value: $3.5M trend: up sentiment: positivecolumns: 1\`\`\`
\`\`\`ui:callout=_newLogostype: tiptitle: New Logos This Weekcontent: Acme Corp (Enterprise, $120K ARR) and Dune Systems (Mid-Market, $28K ARR).\`\`\`
\`\`\`ui:callout=_atRisktype: warningtitle: At-risk Dealscontent: 3 deals past 90-day mark. SDR follow-up scheduled for Thursday.\`\`\`
\`\`\`ui:rows=_rightStackgap: 1remchildren: [newLogos, atRisk]\`\`\`
\`\`\`ui:columnsratio: [3, 2]gap: 1.5remchildren: [pipeline, rightStack]\`\`\``;
<GlyphPreview client:only="react" source={classicSplit} />
<details><summary>View Glyph Markdown source</summary>
````markdown```ui:kpi=_pipelinetitle: Q1 Pipelinemetrics: - label: Deals Open value: "84" trend: up sentiment: positive - label: Win Rate value: "34%" trend: flat sentiment: neutral - label: Forecast value: $3.5M trend: up sentiment: positivecolumns: 1type: tiptitle: New Logos This Weekcontent: Acme Corp (Enterprise, $120K ARR) and Dune Systems (Mid-Market, $28K ARR).type: warningtitle: At-risk Dealscontent: 3 deals past 90-day mark. SDR follow-up scheduled for Thursday.gap: 1remchildren: [newLogos, atRisk]ratio: [3, 2]gap: 1.5remchildren: [pipeline, rightStack]</details>
### Four-quadrant dashboard — 2×2 grid
Two `ui:rows` blocks (each with two children) placed side by side in `ui:columns`:
export const fourQuad = `\`\`\`ui:kpi=_mrrmetrics: - label: MRR value: $182K trend: up sentiment: positive - label: Growth value: "+8.4%" trend: up sentiment: positivecolumns: 2\`\`\`
\`\`\`ui:callout=_featuretype: tiptitle: Top Feature Requestcontent: Export to PDF — 142 upvotes. Shipped in v2.4.\`\`\`
\`\`\`ui:callout=_infratype: infotitle: Infrastructurecontent: All services nominal. Maintenance window Mar 16, 02:00–04:00 UTC.\`\`\`
\`\`\`ui:kpi=_npsmetrics: - label: NPS value: "67" trend: up sentiment: positive - label: CSAT value: "4.6/5" trend: up sentiment: positivecolumns: 2\`\`\`
\`\`\`ui:rows=_leftgap: 1remchildren: [mrr, feature]\`\`\`
\`\`\`ui:rows=_rightgap: 1remchildren: [infra, nps]\`\`\`
\`\`\`ui:columnsratio: [1, 1]gap: 1.5remchildren: [left, right]\`\`\``;
<GlyphPreview client:only="react" source={fourQuad} />
---
## ui:panel — styled wrapper
Wrap any block (including layout blocks) in a visual container:
export const panelExample = `\`\`\`ui:callout=_msgtype: tiptitle: Q1 Objectivecontent: Reach $50M ARR by March 31. Currently at 98.7% of target.\`\`\`
\`\`\`ui:panelchild: msgstyle: elevatedpadding: 1.5rem\`\`\``;
<GlyphPreview client:only="react" source={panelExample} />
Four styles are available: `card` (default), `bordered`, `elevated`, `ghost`. See the [Panel reference](/glyphjs/components/panel/) for details.
### Wrapping a layout block in a panel
`ui:panel` accepts any suppressed variable, including `ui:columns` and `ui:rows`. Define the layout block with `=_name` to suppress it, then reference it from the panel:
export const panelWrapLayout = `\`\`\`ui:callout=_ltype: infotitle: Leftcontent: Left side content.\`\`\`
\`\`\`ui:callout=_rtype: warningtitle: Rightcontent: Right side content.\`\`\`
\`\`\`ui:columns=_gridratio: [1, 1]gap: 1remchildren: [l, r]\`\`\`
\`\`\`ui:panelchild: gridstyle: cardpadding: 1.25rem\`\`\``;
<GlyphPreview client:only="react" source={panelWrapLayout} />
---
## Rules and limitations
- **Order matters.** A suppressed block must be defined before the layout block that references it. Forward references are not supported.- **Shared references.** The same suppressed variable can be referenced by multiple layout blocks. Both layouts point to the same compiled `Block` object — changes to one affect both (though this is irrelevant at render time since the IR is read-only).- **No text flow.** Layout components create structural 2D grids. Prose text does not wrap around blocks — this is explicitly out of scope.- **`glyph-id` on layout children.** Avoid using `glyph-id` on a suppressed block that is used as a layout child. The block is embedded in `block.children`, not in the top-level `ir.blocks`, so cross-block references won't resolve.