# UI Composition `deft/ui/compose` is how interfaces are created in Deft. Components are constructed with flags and composed with `|>` pipelines into a widget tree; the nested tagged-map tree is the underlying representation, not the interface-building API.
from "deft/ui/compose" import div, child, children, props
from "deft/ui/components/column" import column
from "deft/ui/components/row" import row
from "deft/ui/components/label" import label
from "deft/ui/components/text-input" import text-input
## Construction Every component constructor accepts a trailing options map or `--flags`, validated against the component's `deftype` schema (missing fields get their defaults):
[label --text "Hello" --fg "#ffffff"]
[text-input --id "query" --placeholder "Search…" --flex 1]
[column --direction :vertical --bg "#1e1e2e"]
Flags are the preferred construction form. The batch map form is the same constructor and remains valid — useful when props are computed as a map, and best avoided inline:
[label %{ text "Hello" :fg "#ffffff" }]
`[div]` is the generic vertical root (an empty `column`). ## Composition | Helper | Signature | Behaviour | |---|---|---| | `child` | `{| w c |}` | Append one child. A vector appends each entry; `nil` is skipped; a scalar wraps in a `label` | | `children` | `{| w ws |}` | Replace the child list; flattens nested vectors; skips `nil` | | `props` | `{| w opts |}` | Merge a batch of typed options (trailing map or `--flags`) | | field setters | `{| w v |}` | One per prop: `id fg bg flex gap padding direction min-width min-height tab-index visible tooltip scroll-y layer z bounds text value checked items selected wrap read-only focusable expanded on-click on-change on-key on-scroll on-focus on-blur` | | `field` | `{| w k v |}` | Set an arbitrary key — escape hatch for props without a setter | All helpers take the widget first, so they compose in a pipeline, and all preserve the widget's tagged type (protocol dispatch is unaffected).
set title
    [label --text "Results"]
    |> [props --fg "#cfd8dc"]

set toolbar
    [row --gap 1 --min-height 1 --flex 0]
    |> [child [text-input --id "query" --flex 1]]
    |> [child [button --text "Go" --on-click on-go]]
    |> [props --bg "#263238"]

set body
    [column --direction :vertical --bg "#1e1e2e"]
    |> [children @{ $title [label --text "…"] }]
## Pipelines Are Statement-Level `|>` threads at statement/assignment position. Inside a `@{}` literal (e.g. a `children` list) you cannot start a nested pipeline — bind the subtree to a local first, then reference it:
# ✅ bind first, compose by reference
set header [row --flex 0] |> [child [label --text "Header"]]
set panel  [column] |> [children @{ $header $viewer }]

# ❌ a pipeline nested inside a map literal is not a statement
[column] |> [children @{ [row] |> [child ...] }]
## Coercion Rules `child` / `children` reduce the usual wiring noise: - `nil` entries are skipped — safe to write `[child (when visible?)]` style conditionals inline. - Plain strings and numbers become `label`s, so quick placeholders work: `[child "Loading…"]`. - `%MyWidget{…}` values pass through unchanged.
[column]
|> [children @{
     $optional-banner        # nil → dropped
     "Section"               # → label
     [button --text "Retry"] }]
## Props and Type Checking `props` uses the shared `UIProps` schema, so known fields are type-checked at runtime (`--gap "nope"` raises) and defaults are filled by the component itself. Use `field` only for keys the schema doesn't know:
[select-list --items $xs --selected 2]
|> [props %{ :min-height 6 :follow-selected false }]
|> [field :custom-key "value"]
## Under the Hood: The Tree Is a Map Composition builds a tree of tagged maps (`%UIColumn{}`, `%UILabel{}`, …). This representation is an implementation detail — app code composes with the helpers above and treats the tree as opaque — but knowing its shape explains the framework features you'll meet in Event Dispatch and App Platform: - `tui/set-widget-tree` registers the root; `find-by-id` / `replace-by-id` locate and patch subtrees by `:id`. - `preserve-state` carries scroll/selection across rebuilds by matching structure. - Data-driven props are still plain data: `tree` nodes are `%{ :id :label :children … }` maps, `grid` rows are vectors of maps. - Custom widgets declare their own `deftype … << UIWidget %{ … }` and are constructed like any other component — see Widgets and Protocols. Reach for the raw maps (`assoc`/`merge`/`field`) only when mutating an existing tree in place; creating an interface is always composition. ## Quick Reference | Form | Purpose | |---|---| | `[label --text "hi"]` | Construct with flags | | `[div]` | Generic vertical root | | `\|> [child $w]` | Append a child | | `\|> [children @{ … }]` | Replace children | | `\|> [gap 1]` | Set one prop | | `\|> [props --gap 1 --bg "#000"]` | Batch typed props | | `\|> [field :k v]` | Arbitrary key escape hatch | ## See Also - Widgets and Protocols — the component library and protocol model - Event Dispatch — how the tree receives events - Graphics — what custom widgets draw with