Layout model
Layout in the Milda Language uses the propose/choosemodel: the parent proposes available space, the child reports the size it wants, the parent positions it. It's the model SwiftUI, Compose, and Flutter already are - which makes the web, not native, the target that needs translation.
#Propose/choose, in plain language
Every layout negotiation is the same three-step conversation. A container tells each child how much room is on offer; the child answers with the size it wants - its content size, all of it, a share, a fixed amount; the container then places the children. That's the entire model. CSS flexbox can be expressed inpropose/choose, but not the reverse - so the abstract model is the native one, and the web generator translates. Grow/shrink factors, min-width gotchas, and the rest of flexbox's mechanics are realization details the language never exposes.
#Containers
stack- the universal container, flowing children alongrow,column, orlayers(the z axis - how overlays stack).grid- 2D tracks, deliberately basic: equal or weighted columns/rows only.flow- wrap when out of space. Not a third primitive: it's a convenience macro forstack+ thewrapfacet (FlowRow / flex-wrap).
#Container facets
gap- spacing between children, token-valued.inset- padding, block/inline or per-edge.distribute- main-axis distribution:start | center | end | between | around | evenly.align- cross-axis alignment:start | center | end | stretch | baseline.wrap-nowrap | wrap | wrap-reverse.
#Child sizing: per-axis intents
A child declares a sizing intent per axis - width and height independently (width fill, height fit is common). This replaces the grow/shrink/basis triple with five words:
fit- size to content (the default). Webflex: none; SwiftUI ideal size; ComposewrapContent.fill- take all proposed space. Webflex: 1(plus themin: 0the generator knows to add); SwiftUImaxWidth: .infinity; ComposefillMax.weight(n)- a proportional share of the leftover space.fixed(token)- an explicit dimension, token-valued.ratio(w:h)- aspect-locked.
Optional min/max clamps attach per axis on top of any intent.
#Position: escaping the flow
flow- in the normal parent-directed flow (the default).overlay- out of flow, into alayersstack; how badges sit on avatars and popovers float.pinned(edge)- stuck to an edge: sticky headers, safe-area pinning. Safe areas are native-first (notch, home indicator, system bars); the web approximates viaenv(safe-area-inset)in the generator.bleed(edge)- stays in flow but cancels the parent's inset on the named edges, extending the child to the parent's edge (the full-bleed image inside a padded card).
Both pinned and bleed name logical edges (block-start, inline-end…) so layouts survive right-to-left locales; physical edges exist only at the realization layer.
#Conditional layout
A part can carry a default layout plus prop-conditioned overrides - layout when orientation=horizontal { … } - the principled home for a prop-driven structure switch (a data list rendering as a column vs a two-track grid). Facet values that vary by prop or viewport use context aliasing instead (see Facets & tokens).
#What deliberately stays out
The model is intentionally smaller than CSS. Things every platform does differently enough that abstracting them would be a lie are left to per-target realization:
- Advanced grid tracks -
fr,minmax,auto-fitare web-leaning concepts with no faithful native equivalent. - Baseline alignment - exists everywhere, but composes differently with multi-line text and mixed sizes on each platform.
- Scroll-container negotiation - nested-scroll cross-axis sizing diverges between platforms.
Text truncation and wrapping are not in this list - they are style, not layout, and live on the text facet (text.clamp, text.wrap).
Next, put all four layers to work: Authoring walkthrough.