Layout¶
maya uses a flexbox layout engine — the same model as CSS Flexbox and React
Native. Every BoxElement (created by v(), h(), or the BoxBuilder) is a
flex container. Children are laid out along a main axis with configurable
alignment, spacing, sizing, and wrapping.
Direction¶
The main axis determines how children are stacked:
v(child1, child2, child3) // Column: top to bottom (↓)
h(child1, child2, child3) // Row: left to right (→)
Under the hood, these correspond to FlexDirection::Column and
FlexDirection::Row. There are also reverse variants available through the
runtime BoxBuilder:
| Direction | Flow | DSL |
|---|---|---|
Column |
Top → Bottom | v(...) |
Row |
Left → Right | h(...) |
ColumnReverse |
Bottom → Top | (BoxBuilder only) |
RowReverse |
Right → Left | (BoxBuilder only) |
Padding¶
Padding adds space inside the box, between the border and the content:
v(...) | pad<1> // 1 cell on all four sides
v(...) | pad<1, 2> // 1 vertical (top/bottom), 2 horizontal (left/right)
v(...) | pad<1, 2, 3, 4> // top=1, right=2, bottom=3, left=4
v(...) | pad<0, 1> // 0 vertical, 1 horizontal (common for tight borders)
Compile-time validated — negative values produce a static_assert failure.
Gap¶
Gap adds space between children (not before the first or after the last):
Gap works in both directions — vertical gap for v(), horizontal gap for h().
Grow¶
Grow controls how children share remaining space after fixed-size items are placed. A child's share is proportional to its grow factor:
Without grow_, children only take their natural width/height. With grow_,
they expand to fill all available space.
// Push items apart:
h(t<"Left">, space, t<"Right">)
// `space` is SpacerNode with grow=1, pushing "Right" to the far edge
Borders¶
Borders draw a frame around the box. The border line is 1 cell wide on each active side.
Border Styles¶
v(...) | border_<Round> // ╭──────╮
// │ │
// ╰──────╯
v(...) | border_<Single> // ┌──────┐
// │ │
// └──────┘
v(...) | border_<Double> // ╔══════╗
// ║ ║
// ╚══════╝
v(...) | border_<Thick> // ┏━━━━━━┓
// ┃ ┃
// ┗━━━━━━┛
Additional styles available via the runtime BoxBuilder:
| Style | Characters | Description |
|---|---|---|
Single |
┌ ─ ┐ │ |
Standard single line |
Double |
╔ ═ ╗ ║ |
Double line |
Round |
╭ ─ ╮ │ |
Rounded corners |
Bold |
┏ ━ ┓ ┃ |
Thick/bold line |
Classic |
+ - \| + |
ASCII-only |
Arrow |
Arrow characters | Directional |
Border Color (Type-State Safety)¶
Border color can only be set after a border style — enforced at compile time:
This is type-state safety: the bcol operator has a requires (Cfg.has_border)
constraint that only matches BoxNodes that already have a border configured.
Border Title (Runtime API)¶
Border titles are available through the BoxBuilder:
dyn([&] {
return vstack()
.border(BorderStyle::Round)
.border_color(Color::rgb(60, 65, 80))
.border_text("Panel Title", BorderTextPos::Top)
.padding(0, 1, 0, 1)(
text("content")
);
})
Title positioning:
| Position | Placement |
|---|---|
BorderTextPos::Top |
In the top border line |
BorderTextPos::Bottom |
In the bottom border line |
Title alignment:
| Alignment | Effect |
|---|---|
BorderTextAlign::Start |
Left-aligned (default) |
BorderTextAlign::Center |
Centered |
BorderTextAlign::End |
Right-aligned |
Border Sides¶
Control which sides have borders:
// Runtime BoxBuilder only:
box()
.border(BorderStyle::Single)
.border_sides(BorderSides::horizontal()) // Top and bottom only
// ...
box()
.border(BorderStyle::Single)
.border_sides(BorderSides{.top = true, .bottom = true})
// ...
| Factory | Active Sides |
|---|---|
BorderSides::all() |
Top, Right, Bottom, Left |
BorderSides::none() |
None |
BorderSides::horizontal() |
Top and Bottom |
BorderSides::vertical() |
Left and Right |
Alignment¶
Align Items (Cross-Axis)¶
Controls how children are positioned on the cross axis:
// In a row (h), cross axis is vertical:
h(small, tall) // align_items controls vertical alignment
// In a column (v), cross axis is horizontal:
v(narrow, wide) // align_items controls horizontal alignment
Available via the runtime BoxBuilder:
hstack().align_items(Align::Center)(child1, child2)
vstack().align_items(Align::Stretch)(child1, child2)
| Value | Effect |
|---|---|
Align::Start |
Pack to start of cross axis |
Align::Center |
Center on cross axis |
Align::End |
Pack to end of cross axis |
Align::Stretch |
Stretch to fill cross axis |
Align::Baseline |
Align by text baseline |
Justify Content (Main Axis)¶
Controls spacing along the main axis:
| Value | Effect |
|---|---|
Justify::Start |
Pack to start |
Justify::Center |
Center items |
Justify::End |
Pack to end |
Justify::SpaceBetween |
Even space between items |
Justify::SpaceAround |
Even space around items |
Justify::SpaceEvenly |
Even space including edges |
Size Constraints¶
The BoxBuilder supports explicit sizing:
box()
.width(Dimension::fixed(40)) // Exactly 40 columns
.height(Dimension::percent(50)) // 50% of parent height
.min_width(Dimension::fixed(20)) // At least 20 columns
.max_height(Dimension::fixed(10)) // At most 10 rows
Dimension Types¶
Dimension::auto_() // Size to content (default)
Dimension::fixed(40) // Exactly 40 cells
Dimension::percent(50) // 50% of parent's size
50_pct // User-defined literal for percent
Overflow¶
Controls what happens when children exceed the container's bounds:
| Value | Effect |
|---|---|
Overflow::Visible |
Content flows past edges (default) |
Overflow::Hidden |
Content is clipped at edges |
Overflow::Hidden pushes a clip rectangle onto the canvas — child content
outside the box's bounds is not drawn.
Flex Shrink and Basis¶
Shrink¶
When children overflow the container, shrink controls how much each child
contracts. Default is 1 (all children shrink equally):
box().shrink(0)(child) // This child won't shrink
box().shrink(2)(child) // This child shrinks twice as fast
Basis¶
The initial size before grow/shrink is applied:
Margin¶
Margin adds space outside the box:
// Runtime BoxBuilder only:
box().margin(1)(child) // 1 cell on all sides
box().margin(1, 2)(child) // 1 vertical, 2 horizontal
box().margin(1, 2, 3, 4)(child) // top, right, bottom, left
Margin creates space between sibling elements. Unlike padding (inside the border), margin is outside.
Wrap¶
When children exceed the container's main axis, wrapping flows them to the next line:
| Value | Effect |
|---|---|
FlexWrap::NoWrap |
Single line, overflow (default) |
FlexWrap::Wrap |
Wrap to next line |
FlexWrap::WrapReverse |
Wrap in reverse direction |
Layout Shortcuts¶
The DSL namespace provides pre-configured runtime builders for containers that need runtime-configured borders, colors, or titles:
vstack() // BoxBuilder with direction=Column
hstack() // BoxBuilder with direction=Row
center() // BoxBuilder with justify=Center, align_items=Center, grow=1
Common Layout Patterns¶
Sidebar + Main¶
Header / Body / Footer¶
v(
h(t<"App Title"> | Bold, space, t<"v1.0"> | Dim),
v(t<"Main content">) | grow_<1>,
h(t<"Status: OK"> | Dim)
) | pad<1>
Centered content¶
// Using the center() builder:
dyn([&] {
return center()(
text("Loading...", Style{}.with_bold())
);
})
Card¶
v(
t<"Card Title"> | Bold,
t<"">,
t<"Card body text goes here."> | Dim
) | border_<Round> | bcol<60, 65, 80> | pad<1>
Equal-width columns¶
API compatibility¶
All layout patterns work identically in both run() (simple API) and
run<P>() (Program architecture). In simple run(), your render closure
returns an Element. In Program apps, view(const Model&) returns an
Element. The DSL tree — v(), h(), pad, grow, borders, gaps — is
the same either way.