docs/reference/windows-and-layout.md

# Windows and Layout

Every Plushie app starts with a window. Inside it, you compose layout
containers to arrange widgets on screen. This reference covers windows,
sizing, spacing, alignment, and all layout containers.

## Window

`Plushie.Widget.Window`

The top-level container. Every `view/1` must return one or more window
nodes. Windows map to native OS windows, each with its own title bar,
size, position, and optional theme.

```elixir
window "main", title: "My App", theme: :dark do
  column width: :fill, height: :fill do
    # app content
  end
end
```

### Window props

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `title` | string | *n/a* | Title bar text |
| `size` | `{w, h}` | *n/a* | Initial size in pixels |
| `width` | number | *n/a* | Width (alternative to `size`) |
| `height` | number | *n/a* | Height (alternative to `size`) |
| `position` | `{x, y}` | *n/a* | Initial position |
| `min_size` | `{w, h}` | *n/a* | Minimum dimensions |
| `max_size` | `{w, h}` | *n/a* | Maximum dimensions |
| `maximized` | boolean | `false` | Start maximized |
| `fullscreen` | boolean | `false` | Start fullscreen |
| `visible` | boolean | `true` | Whether window is visible |
| `resizable` | boolean | `true` | Allow resizing |
| `closeable` | boolean | `true` | Show close button |
| `minimizable` | boolean | `true` | Allow minimizing |
| `decorations` | boolean | `true` | Show title bar and borders |
| `transparent` | boolean | `false` | Transparent window background |
| `blur` | boolean | `false` | Blur window background |
| `level` | atom | `:normal` | Stacking level (`:normal`, `:always_on_top`, `:always_on_bottom`) |
| `exit_on_close_request` | boolean | `true` | Whether closing exits the app |
| `scale_factor` | number | *n/a* | DPI scale override |
| `theme` | atom or map | *n/a* | Per-window theme (`:dark`, `:nord`, `:system`, or custom) |
| `a11y` | map | *n/a* | Accessibility overrides |

### Multi-window

Return multiple windows from `view/1`:

```elixir
def view(model) do
  [
    window "main", title: "App" do
      main_content(model)
    end,

    if model.show_settings do
      window "settings", title: "Settings", exit_on_close_request: false do
        settings_content(model)
      end
    end
  ]
end
```

`exit_on_close_request: false` on secondary windows means closing them
removes the window without exiting the app. Window IDs must be stable
strings. A changing ID causes a close and re-open.

See [App Lifecycle](app-lifecycle.md#daemon-mode) for daemon mode
(keep running after all windows close) and
[App Lifecycle](app-lifecycle.md#window-sync) for how the runtime
syncs window state.

## Sizing

Every widget has `width:` and `height:` props that control how much
space it occupies. Four value forms are supported:

| Value | Behaviour |
|---|---|
| `:shrink` | Take only as much space as the content needs. This is the default for most widgets. |
| `:fill` | Take all available space in the parent container. |
| `{:fill_portion, n}` | Take a proportional share of available space relative to siblings. |
| number | Exact pixel size. |

### How fill_portion works

When multiple siblings use `:fill` or `{:fill_portion, n}`, the
available space (after fixed-size and `:shrink` siblings are measured)
is divided proportionally. The numbers are relative ratios:

```elixir
row width: :fill do
  container "sidebar", width: {:fill_portion, 1} do ... end
  container "main", width: {:fill_portion, 3} do ... end
end
```

Sidebar gets 1/4 of the width, main gets 3/4. `{:fill_portion, 1}` and
`{:fill_portion, 3}` is the same ratio as `{:fill_portion, 2}` and
`{:fill_portion, 6}`.

`:fill` is shorthand for `{:fill_portion, 1}`. Two `:fill` siblings
split space equally.

### Sizing resolution order

The layout engine processes siblings in this order:

1. **Fixed-size** children (explicit pixel values) are measured first
2. **`:shrink`** children are measured at their intrinsic content size
3. **`:fill` / `{:fill_portion, n}`** children divide the remaining space

This means a fixed-width sidebar always gets its pixels, a shrink button
takes what it needs, and fill containers expand to use whatever is left.

### Constraints

`max_width:` and `max_height:` set upper bounds. A `:fill` child with
`max_width: 600` expands to fill available space but never exceeds 600
pixels. These are available on `column`, `row`, `container`, and
`keyed_column`.

## Padding

`Plushie.Type.Padding`

Padding is the space between a container's edges and its content.
Multiple input forms are accepted:

| Input | Result |
|---|---|
| `16` | 16px on all sides |
| `{8, 16}` | 8px top/bottom, 16px left/right |
| `%{top: 16, bottom: 8}` | Per-side (unset sides default to 0) |
| Do-block | `padding do top 16; bottom 8 end` |

Padding reduces the space available to children. A 200px-wide container
with `padding: 16` has 168px of content space.

## Spacing

Spacing is the gap between sibling children inside a container. Set via
the `spacing:` prop on `column`, `row`, `grid`, and `keyed_column`:

```elixir
column spacing: 12 do
  text("a", "First")    # 12px gap below
  text("b", "Second")   # 12px gap below
  text("c", "Third")    # no gap after last child
end
```

Spacing applies between children, not before the first or after the
last. It does not interact with padding; they are independent.

## Alignment

`Plushie.Type.Alignment`

Alignment controls how children are positioned within a container's
available space.

| Prop | Container | Values |
|---|---|---|
| `align_x:` | `column`, `container` | `:left` (default), `:center`, `:right` |
| `align_y:` | `row`, `container` | `:top` (default), `:center`, `:bottom` |

`column` aligns children horizontally (they already stack vertically).
`row` aligns children vertically (they already flow horizontally).
`container` supports both axes since it has a single child.

The `center: true` shorthand on `container` sets both `align_x: :center`
and `align_y: :center`.

## Layout containers

### column

Arranges children vertically, top to bottom.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `spacing` | number | `0` | Vertical gap between children |
| `padding` | Padding | `0` | Inner padding |
| `width` | Length | `:shrink` | Column width |
| `height` | Length | `:shrink` | Column height |
| `max_width` | number | *n/a* | Maximum width in pixels |
| `align_x` | `:left \| :center \| :right` | `:left` | Horizontal alignment of children |
| `clip` | boolean | `false` | Clip children that overflow |
| `wrap` | boolean | `false` | Wrap children to next column on overflow |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

`wrap: true` enables multi-column flow layout. When children exceed the
column height, they wrap to a new column to the right (like CSS
`flex-wrap`).

### row

Arranges children horizontally, left to right.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `spacing` | number | `0` | Horizontal gap between children |
| `padding` | Padding | `0` | Inner padding |
| `width` | Length | `:shrink` | Row width |
| `height` | Length | `:shrink` | Row height |
| `max_width` | number | *n/a* | Maximum width in pixels |
| `align_y` | `:top \| :center \| :bottom` | `:top` | Vertical alignment of children |
| `clip` | boolean | `false` | Clip children that overflow |
| `wrap` | boolean | `false` | Wrap children to next row on overflow |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

`wrap: true` enables multi-row flow layout. Useful for tag clouds,
toolbar buttons, or any content that should reflow at different widths.

### container

Single-child wrapper for styling, scoping, and alignment.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `padding` | Padding | `0` | Inner padding |
| `width` | Length | `:shrink` | Container width |
| `height` | Length | `:shrink` | Container height |
| `max_width` | number | *n/a* | Maximum width |
| `max_height` | number | *n/a* | Maximum height |
| `align_x` | `:left \| :center \| :right` | `:left` | Horizontal child alignment |
| `align_y` | `:top \| :center \| :bottom` | `:top` | Vertical child alignment |
| `center` | boolean | `false` | Center child in both axes |
| `clip` | boolean | `false` | Clip child that overflows |
| `background` | Color or Gradient | *n/a* | Background fill |
| `color` | Color | *n/a* | Text colour override |
| `border` | Border | *n/a* | Border specification |
| `shadow` | Shadow | *n/a* | Drop shadow |
| `style` | atom or StyleMap | *n/a* | Named preset or full style map |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Container style presets: `:transparent`, `:rounded_box`, `:bordered_box`,
`:dark`, `:primary`, `:secondary`, `:success`, `:danger`, `:warning`.

Container serves three roles: **styling** (background, border, shadow),
**scoping** (named containers create ID scopes for their children; see
[Scoped IDs](scoped-ids.md)), and **alignment** (positioning a child
within available space).

### scrollable

Adds scroll bars when content overflows. Requires an explicit string ID
because the renderer tracks scroll position as internal state.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `width` | Length | `:shrink` | Scrollable area width |
| `height` | Length | `:shrink` | Scrollable area height |
| `direction` | `:vertical \| :horizontal \| :both` | `:vertical` | Scroll direction |
| `spacing` | number | *n/a* | Gap between scrollbar and content |
| `scrollbar_width` | number | *n/a* | Scrollbar track width |
| `scrollbar_margin` | number | *n/a* | Margin around scrollbar |
| `scroller_width` | number | *n/a* | Scroller handle width |
| `scrollbar_color` | Color | *n/a* | Scrollbar track colour |
| `scroller_color` | Color | *n/a* | Scroller thumb colour |
| `anchor` | `:start \| :end` | `:start` | Scroll anchor position |
| `on_scroll` | boolean | `false` | Emit scroll events with viewport data |
| `auto_scroll` | boolean | `false` | Auto-scroll to show new content |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

`auto_scroll: true` is useful for chat-style interfaces where new
messages should scroll into view. `anchor: :end` starts scrolled to
the bottom.

When `on_scroll: true`, scroll events include viewport metrics:
`absolute_x`, `absolute_y`, `relative_x`, `relative_y`, `bounds`
(visible area as `{width, height}`), and `content_bounds` (full content
as `{width, height}`).

### keyed_column

Like `column`, but uses each child's ID as a diffing key for the
renderer. Same props as column minus `align_x`, `clip`, and `wrap`.

Use `keyed_column` for dynamic lists where items are added, removed,
or reordered. A plain `column` diffs by position index, so adding an
item at the top shifts all widget state down by one. `keyed_column`
matches by ID, preserving widget state (focus, scroll position, cursor)
regardless of position changes.

### stack

Layers children on top of each other (z-axis). First child is at the
back, last child is at the front.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `width` | Length | `:shrink` | Stack width |
| `height` | Length | `:shrink` | Stack height |
| `clip` | boolean | `false` | Clip children that overflow |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Use for overlays, badges, loading spinners, or any situation where
elements need to be layered.

### grid

Arranges children in a grid layout.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `columns` | pos_integer | `1` | Number of columns |
| `spacing` | number | `0` | Gap between cells |
| `width` | number | *n/a* | Grid width in pixels |
| `height` | number | *n/a* | Grid height in pixels |
| `column_width` | Length | *n/a* | Width of each column |
| `row_height` | Length | *n/a* | Height of each row |
| `fluid` | number | *n/a* | Max cell width for fluid auto-wrap mode |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Two modes: **fixed columns** (`columns: 3`) and **fluid** (`fluid: 200`).
In fluid mode, columns auto-wrap based on available width. The number
adjusts to fit as many cells of the specified max width as possible.

### pin

Positions a child at exact pixel coordinates within a container.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `x` | number | `0` | X position in pixels |
| `y` | number | `0` | Y position in pixels |
| `width` | Length | `:shrink` | Pin container width |
| `height` | Length | `:shrink` | Pin container height |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Pin does not participate in flow layout. The child is positioned
absolutely. Useful for tooltips, popovers, or custom positioning.

### floating

Applies translate and scale transforms to a child.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `translate_x` | number | `0` | Horizontal translation in pixels |
| `translate_y` | number | `0` | Vertical translation in pixels |
| `scale` | number | *n/a* | Scale factor |
| `width` | Length | *n/a* | Container width |
| `height` | Length | *n/a* | Container height |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Unlike `pin`, floating applies visual transforms without removing the
child from flow layout. The child still occupies its original space;
the transform is visual only.

### responsive

Adapts layout based on available size by emitting resize events.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `width` | Length | `:fill` | Container width |
| `height` | Length | `:fill` | Container height |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

When the responsive container's size changes, it emits
`%WidgetEvent{type: :resize, data: %{width: w, height: h}}`.
Use this in `update/2` to store the measured size and adjust your
`view/1` based on it (for example, switching from a sidebar layout to
a stacked layout below a certain width).

### space

Invisible spacer widget. No children, no visual output.

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `width` | Length | `:shrink` | Space width |
| `height` | Length | `:shrink` | Space height |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Use for explicit gaps, alignment tricks, or pushing siblings apart in
a row or column.

### pane_grid

Resizable tiled panes with split, close, swap, and drag. Requires an
explicit ID (holds renderer-side state for pane sizes).

| Prop | Type | Default | Purpose |
|---|---|---|---|
| `spacing` | number | `2` | Space between panes |
| `width` | Length | `:fill` | Grid width |
| `height` | Length | `:fill` | Grid height |
| `min_size` | number | `10` | Minimum pane size in pixels |
| `leeway` | number | `min_size` | Grabbable area around dividers |
| `divider_color` | Color | *n/a* | Divider colour |
| `divider_width` | number | *n/a* | Divider thickness |
| `a11y` | map | *n/a* | Accessibility overrides. See [Accessibility](accessibility.md). |

Pane grid emits these events:

| Event type | Data | Description |
|---|---|---|
| `:pane_clicked` | `pane` | Pane selected |
| `:pane_resized` | `split`, `ratio` | Divider moved |
| `:pane_dragged` | `pane`, `target`, `action`, `region`, `edge` | Pane drag (picked/dropped/canceled) |
| `:pane_focus_cycle` | *n/a* | F6/Shift+F6 focus cycling |

Manage pane layout with commands: `Command.pane_split/4`,
`Command.pane_close/2`, `Command.pane_swap/3`,
`Command.pane_maximize/2`, `Command.pane_restore/1`.

## Composition patterns

### Sidebar + content

```elixir
row width: :fill, height: :fill do
  column width: 200, height: :fill, padding: 8 do
    # Fixed-width sidebar
  end
  container "main", width: :fill, height: :fill, padding: 16 do
    # Content fills remaining space
  end
end
```

### Header / body / footer

```elixir
column width: :fill, height: :fill, spacing: 0 do
  row padding: 8 do
    # Header (shrinks to content)
  end
  container "body", width: :fill, height: :fill do
    # Body (fills remaining space)
  end
  row padding: 8 do
    # Footer (shrinks to content)
  end
end
```

### Centred content

```elixir
container width: :fill, height: :fill, center: true do
  text("msg", "Centred in both axes")
end
```

### Scrollable list

```elixir
scrollable "items", height: 400 do
  keyed_column spacing: 4 do
    for item <- model.items do
      container item.id, padding: 8 do
        text(item.id <> "-name", item.name)
      end
    end
  end
end
```

### Overlay / badge

```elixir
stack do
  container width: :fill, height: :fill do
    # Main content underneath
  end
  pin x: 10, y: 10 do
    text("badge", "NEW", size: 10)
  end
end
```

## See also

- [Layout guide](../guides/07-layout.md) - sizing, spacing, and
  alignment applied to the pad
- [Styling reference](themes-and-styling.md) - Border, Shadow, and background
  for containers
- [Scoped IDs reference](scoped-ids.md) - how named containers create
  ID scopes
- [Built-in Widgets reference](built-in-widgets.md) - full widget
  catalog including non-layout widgets
- `Plushie.Type.Length` - Length type encoding
- `Plushie.Type.Padding` - Padding normalisation and encoding
- `Plushie.Type.Alignment` - alignment value encoding