Controls.Stack is the fundamental layout primitive in MeshWeaver's UI system. It arranges child controls in a single axis — vertical by default, or horizontal when you need a toolbar or button row — with full control over spacing, alignment, and wrapping. Stacks compose freely: nest them to build any layout from simple forms to multi-panel dashboards.
Three stack layouts: vertical (items stacked top-to-bottom), horizontal (items arranged left-to-right), and composed nesting for real-world patterns like forms with action bars and dashboards.
Quick Start
The simplest stack is vertical and requires no configuration at all — just chain WithView calls:
Controls.Stack
.WithView(Controls.Html("<b>Stack layout demo</b>"))
.WithView(Controls.Html("First item"))
.WithView(Controls.Html("Second item, directly below"))
.WithView(Controls.Html("Third item, with default spacing"))
Switch to horizontal by adding a single .WithOrientation call:
Controls.Stack
.WithOrientation(Orientation.Horizontal)
.WithHorizontalGap("12px")
.WithView(Controls.Button("Save"))
.WithView(Controls.Button("Cancel"))
Configuration Reference
Every With* method returns a new StackControl instance — the stack is immutable and composable.
| Method | Purpose | Example values |
|---|---|---|
WithOrientation(orientation) |
Layout axis | Orientation.Vertical (default), Orientation.Horizontal |
WithVerticalGap(gap) |
Space between items on the vertical axis | "8px", "1rem", "16px" |
WithHorizontalGap(gap) |
Space between items on the horizontal axis | "8px", "1rem", "16px" |
WithHorizontalAlignment(align) |
Cross-axis (vertical stack) or main-axis (horizontal stack) horizontal alignment. Unset means start; Stretch is an explicit opt-in — see below |
"start", "center", "end", HorizontalAlignment.Stretch |
WithVerticalAlignment(align) |
Cross-axis or main-axis vertical alignment | "start", "center", "end" |
WithWidth(width) |
Explicit stack width | "300px", "100%" |
WithHeight(height) |
Explicit stack height | "200px", "100%" |
WithWrap(wrap) |
Allow items to wrap onto the next row/column | true, false |
Cross-Axis Alignment: Stretch Is an Explicit Opt-In
A vertical stack aligns its children to the START of the cross axis when nothing is set: each child is as wide as its own content. That is right for a lone button or a logo, and it is the default.
It is wrong for content that has no width of its own, and the case that bites is a markdown body
holding a table wider than the column. The body then takes the table's full max-content width, and
the pane clips it at the right edge with no scrollbar (#6036 — a CRM offer page measured 3,211 px
inside a 980 px column). For such a column, ask for HorizontalAlignment.Stretch
(align-items: stretch): every child is as wide as the column, and the table wraps or scrolls inside it.
Controls.Stack
.WithWidth("100%")
.WithHorizontalAlignment(HorizontalAlignment.Stretch)
.WithView(Controls.Markdown(body))
The framework does this itself on its markdown page columns — the node page's outer column
(MeshNodeLayoutAreas.BuildDetailsTemplate) and the Markdown node's overview container
(MarkdownOverviewLayoutArea.BuildOverview). Every other stack keeps start alignment, so no existing
layout shifts.
On a horizontal stack HorizontalAlignment is the main axis (justify-content), where Stretch
behaves as start. The value is serialised by name, and the Blazor client parses it case-insensitively
into Fluent UI's own HorizontalAlignment.Stretch, so HorizontalAlignment.Stretch and the string
"stretch" mean the same thing.
Adding Child Controls
WithView has several overloads to cover static, dynamic, and context-aware content:
// Static control
.WithView(Controls.Label("Text"))
// Named area (useful for targeted updates)
.WithView(Controls.Button("Click"), "buttonArea")
// Dynamic — updates whenever the stream emits a new value
.WithView(dataStream.Select(d => Controls.Label(d.Name)))
// Context-aware — access the render context inside the factory
.WithView((host, ctx) => Controls.Label($"Area: {ctx.Area}"))
Common Patterns
Right-Aligned Button Group
Pair Orientation.Horizontal with WithHorizontalAlignment("end") to push a button row to the right edge — the standard footer pattern for dialogs and forms:
Controls.Stack
.WithOrientation(Orientation.Horizontal)
.WithHorizontalGap("8px")
.WithHorizontalAlignment("end")
.WithView(Controls.Button("Cancel"))
.WithView(Controls.Button("Save"))
Form with Action Bar
Nest a horizontal button stack inside a vertical form stack to separate the editor from its actions cleanly:
Controls.Stack
.WithVerticalGap("16px")
.WithView(host.Edit(new UserData()))
.WithView(
Controls.Stack
.WithOrientation(Orientation.Horizontal)
.WithHorizontalGap("8px")
.WithHorizontalAlignment("end")
.WithView(Controls.Button("Cancel"))
.WithView(Controls.Button("Submit"))
)
Multi-Panel Dashboard
Outer vertical stack for header/content/footer, inner horizontal stack for side-by-side panels:
Controls.Stack
.WithView(Controls.Html("<h1>Dashboard</h1>"))
.WithView(
Controls.Stack
.WithOrientation(Orientation.Horizontal)
.WithHorizontalGap("16px")
.WithView(BuildLeftPanel())
.WithView(BuildRightPanel())
)
.WithView(Controls.Html("<footer>Footer</footer>"))
Skin Properties
The underlying LayoutStackSkin record maps directly to the With* methods above. You will encounter these property names when inspecting serialized layout state or writing custom renderers:
| Property | Type | Default |
|---|---|---|
Orientation |
object? |
Orientation.Vertical |
HorizontalAlignment |
object? |
null (the client renders start) |
VerticalAlignment |
object? |
null |
HorizontalGap |
object? |
null |
VerticalGap |
object? |
null |
Wrap |
object? |
null |
Width |
object? |
null |
Height |
object? |
null |
See Also
- Container Control — overview of all container types
- Editor Control — auto-generated form editors
- DataGrid Control — tabular data display