Skip to content

Stack and layer

A stack lays its children out along one axis. A layer draws every child across the whole box, the first one furthest back.

ui.stack / ui.layer

new UiStack
{
Key = "row",
Direction = UiComponentDirections.Horizontal,
Align = UiComponentAlignments.Center,
Gap = UiSize.FromBasis(0.025),
Padding = UiSize.FromBasis(0.02),
Children =
[
new UiImage { Key = "art", Source = cover, Size = UiSize.FromBasis(0.09) },
new UiStack
{
Key = "labels",
Fill = true,
Justify = UiComponentJustify.Center,
Gap = UiSize.FromBasis(0.004),
Children =
[
new UiTextRun { Key = "title", Text = item.Title, Size = UiSize.FromBasis(0.038), Weight = UiComponentTextWeights.Medium },
new UiTextRun { Key = "subtitle", Text = item.Subtitle, Size = UiSize.FromBasis(0.032), Role = UiComponentTextRoles.Secondary },
],
},
],
}

A row with a square cover on the left and a white title above a grey subtitle filling the rest

A track row from the built-in music picker: cover art on the left, two lines of text filling the rest.

new UiStack
{
Key = "header",
Direction = UiComponentDirections.Horizontal,
Children =
[
new UiTextRun { Key = "name", Text = "Office", Size = 0.12, Fill = true },
new UiTextRun { Key = "temp", Text = "21°", Size = 0.12, MainSize = 0.3, Align = UiComponentAlignments.End },
],
}
+--------------------------+--------+
| Office | 21° |
+--------------------------+--------+

A wide tile with Office at the leading edge and 21° at the trailing edge

Fill takes whatever the siblings leave. A text next to a filling sibling needs its own MainSize, because a reader cannot measure text without a font - see Sizing.

new UiStack
{
Key = "current",
Direction = UiComponentDirections.Horizontal,
Justify = UiComponentJustify.SpaceBetween,
Align = UiComponentAlignments.Center,
Children = [icon, temperature],
}
+-----------------------------------+
| [icon] 21° |
+-----------------------------------+

A wide tile with a sun icon at the leading edge and a bold 21° at the trailing edge

SpaceBetween puts all free space between children and none at the edges.

new UiStack
{
Key = "reading",
Direction = UiComponentDirections.Horizontal,
Align = UiComponentAlignments.Baseline,
Children =
[
new UiTextRun { Key = "value", Text = "73", Size = 0.3 },
new UiTextRun { Key = "unit", Text = "km/h", Size = 0.1 },
],
}

A large 73 with a small km/h beside it, both sitting on the same baseline

Baseline puts the runs’ text on one line instead of aligning their boxes. Only children that draw text take part; anything else in the row aligns to the trailing edge.

new UiStack
{
Key = "card",
Padding = UiSize.FromBasis(0.06),
Gap = UiSize.FromBasis(0.03),
Children = [title, new UiStack { Key = "inset", Padding = 0.04, Children = [body] }],
}

No element has a margin property. Space around one child comes from wrapping it in its own stack with Padding.

new UiStack
{
Key = "feed",
Fill = true,
Overflow = UiComponentOverflows.ClipStart,
Padding = UiSize.FromBasis(0.2 * textSize),
RequiredComponentVersion = 2,
Fallback = new UiStack { Key = "feedFallback", Fill = true, Children = [newestMessage] },
Children = messages,
}

A stack shrinks its children when they need more room than it has. With ClipStart it keeps every child at its natural size instead, puts the content against its end edge and cuts off what does not fit at the start: a vertical feed shows its last children at the bottom and loses the first ones at the top. fill on a child is ignored, and ClipStart overrides Justify. The stack still reports the sum of its children as its own size, so give it its box from its parent with Fill or MainSize. A padding of 0.2 of the text size keeps the descenders of the last line inside the clip.

ClipStart needs component version 2. A version 1 reader ignores overflow and shrinks every child into the box, so ask for version 2 and carry a fallback that reads well shrunk, such as the newest few messages.

new UiLayer
{
Key = "historyGraph",
Children =
[
new UiChart { Key = "chart", Points = points, PlotTop = 0.66 },
new UiStack { Key = "labels", Padding = safeArea, Children = [title, subtitle] },
value,
],
Fallback = value,
}
+----------------------+
| CPU <- labels
| 42 % <- value
| /\_/\__/\ <- chart (furthest back)
+----------------------+

A history graph tile: a filled line chart along the bottom, CPU and Package labels at the top left, and 42 % drawn over the middle

The built-in history graph: a chart edge to edge, labels inset over it. Each layer child that needs padding, gap, justify or align wraps itself in a ui.stack, which also lets two layers inset differently. Fallback gives an older reader the value alone.

Property Values Default Meaning
Direction (direction) UiComponentDirections.Vertical, .Horizontal (vertical, horizontal) vertical The layout axis.
Justify (justify) UiComponentJustify.Start, .Center, .End, .SpaceBetween (start, center, end, space-between) start How free space is distributed along the main axis.
Align (align) UiComponentAlignments.Start, .Center, .End, .Stretch, .Baseline (start, center, end, stretch, baseline) stretch How children are aligned on the cross axis.
Gap (gap) length No gap The gap between children.
Padding (padding) length No padding Inner padding on every edge.
Background (background) #rrggbb or transparent Paints nothing behind its children The stack’s own fill, a literal colour rather than a theme role - see Colours and text. transparent paints nothing; on the widget’s root it also removes the tile face behind the widget.
Overflow (overflow) UiComponentOverflows.Shrink, .ClipStart (shrink, clip-start) shrink What happens to children that do not fit the main axis; clip-start needs component version 2.

No properties of its own: no padding, gap, justify, align or background.

Property Values Default Meaning
MainSize (mainSize) length Measured from content The extent on the parent’s main axis - see Sizing.
Fill (fill) bool Does not fill Takes the parent’s leftover main-axis space - see Sizing.
Answer (answer) string No answer On a dialog surface, the value a press inside it settles the dialog with - see Events.

None of their own. Either can still complete a dialog through Answer when a descendant’s press claims it: the affordance belongs to the declared event, not the node type, so a list row can be a stack (no accent background) and still settle the dialog.

Both: any element, any number, in any combination.

A stack divides its own main-axis extent among its children: each child’s mainSize or fill competes for that budget, and a child declaring neither is measured from what a renderer can work out without a font. A text needs its own mainSize whenever a filling sibling sits next to it.

When the children ask for more than the stack has, they shrink to share the shortfall. With overflow: "clip-start" they keep their natural size instead: fill is ignored, a mainSize still holds, the content sits against the end edge whatever justify says, and what does not fit is clipped at the start edge.

A layer gives every child the whole content box, in declaration order, first one furthest back. mainSize and fill mean nothing on a layer’s children, because there is no axis to divide.

See Sizing for the full model.

  • A stack with no direction lays out vertically, no justify means start, no align means stretch.
  • baseline applies to children that draw text; other children in the row align to the trailing edge.
  • A stack with no background paints nothing behind its children; a layer never paints a background.
  • A background of transparent paints nothing either. When the widget’s root stack (after resolving a ui.responsive root to its drawn variant) carries it, draw the tile without its face fill and shadow, so the folder background shows through; keep the border ring. Reject any other value that is not #rrggbb. A modifier’s background stays #rrggbb only.
  • On a layer, ignore mainSize and fill on children; every child gets the whole box.
  • overflow absent, shrink or a value you do not know: shrink the children as always. clip-start: give each child its natural main size (its mainSize if declared, fill ignored), align the content to the end edge regardless of justify, and clip at the stack’s own box so the first children are the ones cut off. Advertise ui.stack version 2 only once you do this.
  • overflow belongs to ui.stack; a ui.button does not take it.
  • A reader that does not know ui.layer draws the node’s fallback - it is a type, not a third direction, so negotiation catches it.