---
title: The layout tree
description: Every node has a type; containers have children and primitives do not; common layout props apply to any node.
status: shipped
url: https://getairframe.com/framewerk/core/layout-tree
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# The layout tree

Every node has a `type`. Container nodes have `children`; primitives do not — if you can picture the DOM, you can picture a spec.

## Examples

A row of icon + heading:

**A header row**

```json
{
  "formatVersion": "1.0",
  "screens": [
    {
      "id": "menu",
      "layout": {
        "_id": "menu-header",
        "type": "row",
        "gap": 8,
        "align": "center",
        "children": [
          { "_id": "menu-icon", "type": "icon", "name": "coffee" },
          { "_id": "menu-title", "type": "text", "variant": "heading", "content": "Coffee Cart" }
        ]
      }
    }
  ]
}
```

A nested column with spacing, a badge and a primary action:

**A column with actions**

```json
{
  "formatVersion": "1.0",
  "screens": [
    {
      "id": "home",
      "layout": {
        "_id": "home-root",
        "type": "column",
        "gap": 16,
        "padding": 24,
        "background": "surface",
        "radius": "md",
        "children": [
          { "_id": "home-h", "type": "text", "variant": "heading", "content": "Today" },
          { "_id": "home-meta", "type": "row", "gap": 8, "children": [
            { "_id": "home-chip", "type": "chip", "label": "Open" },
            { "_id": "home-badge", "type": "badge", "content": "New", "variant": "accent" }
          ]},
          { "_id": "home-cta", "type": "button", "variant": "primary", "fullWidth": true, "content": "Continue" }
        ]
      }
    }
  ]
}
```

## Anatomy

### `_id`

A stable, human-meaningful identifier for the node. It is not required, but give nodes real names (`"menu-header"`, not `"row-4"`) — `_id` is how patches address a node across edits, and auto-generated-looking IDs are flagged by the validator's lint pass for exactly that reason.

### Common layout props

These apply to **any** node type.

| Prop | Type | Notes |
| --- | --- | --- |
| `gap` | number | Space between children, in px. |
| `padding` | number \| string | `24`, `"12 16"` (v h), or `"8 12 8 12"` (t r b l). |
| `flex` | number | Flex grow factor. |
| `width`, `height` | number \| string | `120`, `"100%"`, `"auto"`. |
| `align` | string | Cross axis: `start`, `center`, `end`, `stretch`. |
| `justify` | string | Main axis: `start`, `center`, `end`, `space-between`, … |
| `scroll` | boolean | Direction-aware: horizontal on `row`, vertical elsewhere. |
| `background` | string | A background token name, a hex value, a CSS gradient string, or a `url(...)` image fill. |
| `border` | string | `all`, `top`, `bottom`, `left`, `right`. |
| `radius` | `"sm" \| "md" \| "lg"` \| number | Corner radius. |
| `elevation` | `0`–`5` | A layered, theme-aware shadow ladder. `shadow` `"sm"`/`"md"`/`"lg"` are aliases for steps 1–3. |
| `shadow` | boolean \| string | `"sm"`, `"md"`, `"lg"` presets — or any CSS box-shadow string. |
| `hover`, `press`, `focus` | object | A CSS bag applied in that state. Semantic colour words and `var(--token)` resolve. |
| `style` | object | Raw CSS past the passthrough allowlist, applied last; `var(--token)` resolves. |
| `animate` | string | Appear animation preset, or a keyframe name the active style defines. Plays on mount; respects `prefers-reduced-motion`. |
| `showWhen` / `hideWhen` | string | Conditional visibility — see [State & expressions](/framewerk/core/state-and-expressions.md). |
| `slot` | string | Marks a container as an inline slot target. |
| `instructions` | string[] | Behavior notes for humans and code generators. Not rendered. |

Motion companions (`animateDuration`, `animateDelay`, `animateEasing`, `animateIterations`, `animateOn`, `animateStagger`, `parallax`, `scrub`, `mask`, `aspectRatio`, `safeArea`) are documented in the [format reference](../spec-format.md) §4 and on the [Motion](../rendering/motion.md) page.

### CSS passthrough

Beyond the semantic props, any node accepts a set of raw CSS properties (camelCase) that forward directly to the rendered element's inline style — on plain nodes and component instances alike. Semantic props win where they overlap; passthrough fills in the rest.

Positioning and box: `position` (including `sticky`), `top`/`right`/`bottom`/`left`, `zIndex`, `overflow`/`overflowX`/`overflowY`, `minWidth`/`minHeight`/`maxWidth`/`maxHeight`, `order`, per-side `margin*`/`padding*`, `boxShadow`, `borderRadius`, per-side `border*`, `backgroundColor`.

Motion: `transition`, `animation`, `animationDelay`, `animationDuration`, `transform`, `transformOrigin`, `willChange`.

Compositing: `opacity`, `mixBlendMode`, `backgroundImage`, `backgroundSize`, `backgroundPosition`, `backgroundRepeat`, `backgroundClip` + `WebkitBackgroundClip`, `WebkitTextFillColor`, `textShadow`, `clipPath`, `maskImage` + `WebkitMaskImage`, `backdropFilter`, `filter`.

Grid: `gridTemplateColumns`, `gridTemplateRows`, `gridColumn`, `gridRow` — an authored template wins over the `columns: N` default.

Typography and text flow: `letterSpacing`, `lineHeight`, `textDecoration`, `textTransform`, `fontStyle`, `whiteSpace`, `textOverflow`, `wordBreak`, `wordWrap`.

Interaction and scroll: `cursor`, `scrollSnapType`, `scrollSnapAlign`.

The second widening added 3D and the standalone transforms (`perspective`, `transformStyle`, `backfaceVisibility`, `rotate`/`scale`/`translate` as strings), font axes (`fontVariationSettings`, `fontFeatureSettings`, `textWrap`, `WebkitLineClamp`, `hyphens`, `textIndent`), `outline*`, `pointerEvents`, `userSelect`, `visibility`, `touchAction`, `inset`, `margin`, the flex and border longhands, per-corner radii, the animation longhands, the scroll properties and `viewTransitionName`.

A property outside the allowlist is stored and **not rendered**, and validation says so. `style: { … }` is the escape hatch for exactly that case — raw CSS on any node, sanitized, applied last.

## Stability

Layout props and the CSS passthrough allowlist are part of the format contract: adding a prop is additive; removing one or narrowing its accepted values is breaking. The allowlist itself is checked against `KNOWN_CSS_PROPERTY_NAMES` in the validator.
