Skip to content

VStackWidget

Arrange child widgets vertically from top to bottom.

VStackWidget is a layout container that positions its children in a vertical column. It automatically manages height distribution, allowing children to size based on content, fill available space, or use fixed heights.

Basic Usage

Create a vertical layout using the fluent API with collection expression syntax:

csharp

Focus Management

VStackWidget manages focus for all its descendant widgets. Use Tab to move focus forward and Shift+Tab to move backward through focusable children.

Simple Vertical Layout

Stack widgets top to bottom:

csharp

All children are positioned from top to bottom with their natural content heights.

Sizing Children

VStackWidget supports three sizing modes for children:

Content Sizing (Default)

Children size to their natural content height:

csharp
ctx.VStack(v => [
    v.Text("Single line"),
    v.Text("Text that wraps to multiple lines when the width is constrained").Wrap(),
    v.Text("Another single line")
])
// Each widget takes exactly the height it needs

Fill Sizing

Children with .Fill() expand to consume remaining space:

csharp

This is commonly used for scrollable content areas that should take all remaining vertical space.

When multiple children use .Fill(), space is distributed evenly:

csharp
ctx.VStack(v => [
    v.Panel(top).Fill(),
    v.Panel(bottom).Fill()
])
// Both panels get 50% of available height

Fixed Sizing

Children with .FixedHeight() take exactly the specified height:

csharp
ctx.VStack(v => [
    v.Text("Header").FixedHeight(3),
    v.List(items, onSelect).Fill(),
    v.Text("Footer").FixedHeight(1)
])
// Header is always 3 rows, footer always 1 row

Weighted Fill

Use weighted fills to distribute space proportionally:

csharp
ctx.VStack(v => [
    v.Panel(header).FillHeight(1),      // Gets 1/4 of space
    v.Panel(main).FillHeight(3)         // Gets 3/4 of space
])

Layout Algorithm

VStackWidget distributes height in two passes:

  1. Fixed Pass: Measure content-sized and fixed-height children, sum their heights
  2. Fill Pass: Distribute remaining height among fill children based on their weights

All children receive the full width of the VStack.

Focus Navigation

VStackWidget provides default keyboard bindings:

KeyAction
TabMove focus to next focusable widget
Shift+TabMove focus to previous focusable widget

Focus order follows the top-to-bottom order of children in the array.

Clipping

Content that extends beyond the VStack's bounds is clipped by default. Children cannot render outside the stack's area.

To allow overflow (not recommended), you would need to configure clipping at a lower level via the layout system.

Common Patterns

Application Layouts

Create header-content-footer layouts:

csharp
ctx.VStack(v => [
    v.Text("═══ My Application ═══"),
    v.Text(""),
    v.Border(b => [
        b.Text("Main content area")
    ]).Fill(),
    v.Text(""),
    v.Text("Status: Ready")
])

Stack menu items vertically:

csharp
ctx.Border(b => [
    b.VStack(v => [
        v.Text("Main Menu"),
        v.Text(""),
        v.Button("New File").OnClick(_ => NewFile()),
        v.Button("Open File").OnClick(_ => OpenFile()),
        v.Button("Save File").OnClick(_ => SaveFile()),
        v.Text(""),
        v.Button("Exit").OnClick(args => args.Context.RequestStop())
    ])
], title: "File")

Form Layouts

Arrange form fields vertically:

csharp
ctx.VStack(v => [
    v.Text("User Registration"),
    v.Text(""),
    v.Text("Name:"),
    v.TextBox(state.Name).OnTextChanged(args => state.Name = args.NewText),
    v.Text(""),
    v.Text("Email:"),
    v.TextBox(state.Email).OnTextChanged(args => state.Email = args.NewText),
    v.Text(""),
    v.HStack(h => [
        h.Button("Submit").OnClick(_ => state.Submit()),
        h.Text("  "),
        h.Button("Cancel").OnClick(_ => state.Cancel())
    ])
])

Split Views

Create vertically split content areas:

csharp
ctx.Border(b => [
    b.VStack(v => [
        v.Border(b2 => [
            b2.Text("Top Panel")
        ]).FillHeight(2),
        v.Border(b2 => [
            b2.Text("Bottom Panel (larger)")
        ]).FillHeight(3)
    ])
])

Text Wrapping Behavior

When a TextWidget inside a VStack has .Wrap() enabled:

  1. VStack passes its current width constraint to the text widget
  2. Text widget measures how many lines it needs at that width
  3. VStack includes the wrapped height in its total height calculation

This ensures wrapped text fits properly in the layout:

csharp
ctx.VStack(v => [
    v.Text("Title"),
    v.Text(""),
    v.Text(
        "This long description will wrap to multiple lines " +
        "based on the available width of the VStack."
    ).Wrap()
])

Combining with HStack

Build complex grid-like layouts:

csharp
ctx.VStack(v => [
    v.HStack(h => [
        h.Border(b => [b.Text("A")]).Fill(),
        h.Border(b => [b.Text("B")]).Fill()
    ]).FixedHeight(5),
    v.HStack(h => [
        h.Border(b => [b.Text("C")]).Fill(),
        h.Border(b => [b.Text("D")]).Fill()
    ]).Fill()
])

Performance Considerations

  • VStack measures all children during layout
  • Content-sized children are measured with the stack's width and unbounded height
  • Fill children are measured after fixed space is allocated
  • Text wrapping calculations can impact performance with many wrapped widgets

Released under the MIT License.