Search documentation of Plushie

# Windows and Layout

Copy Markdown View Source

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.

    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`:

    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 for daemon mode (keep running after all windows close) and App Lifecycle 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:

    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`:

    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.

`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.

`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.

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), 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.

`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.

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.

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.

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.

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.

When the responsive container's size changes, it emits `%WidgetEvent{type: :resize, value: %{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.

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.

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

    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

    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

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

### Scrollable list

    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

    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 - sizing, spacing, and alignment applied to the pad
- Styling reference - Border, Shadow, and background for containers
- Scoped IDs reference - how named containers create ID scopes
- Built-in Widgets reference - 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

← Previous Page Events Reference
Next Page → Mix Tasks