Skip to content

WrapPanelWidget ​

Arrange child widgets sequentially, wrapping to the next row or column when available space is exceeded.

WrapPanelWidget is a layout container that flows children left-to-right (horizontal) or top-to-bottom (vertical), automatically wrapping to the next row or column when a child would exceed the available extent. It works best with uniformly-sized children and is ideal for card grids, tag lists, and responsive item layouts.

Basic Usage ​

Create a horizontal wrap panel using the fluent API with collection expression syntax:

csharp
using Hex1b;

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.WrapPanel(w => [
        w.Border(b => [b.Text("Item 1")]).FixedWidth(15),
        w.Border(b => [b.Text("Item 2")]).FixedWidth(15),
        w.Border(b => [b.Text("Item 3")]).FixedWidth(15),
        w.Border(b => [b.Text("Item 4")]).FixedWidth(15),
        w.Border(b => [b.Text("Item 5")]).FixedWidth(15),
        w.Border(b => [b.Text("Item 6")]).FixedWidth(15),
    ]))
    .Build();

await terminal.RunAsync();

Focus Management

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

Horizontal Wrapping ​

Children flow left-to-right and wrap to the next row when the available width is exceeded:

csharp

Items that don't fit on the current row are moved to the next row. The row height is determined by the tallest child in that row.

Vertical Wrapping ​

Use VWrapPanel to flow children top-to-bottom, wrapping to the next column:

csharp

Items that don't fit in the current column are moved to the next column. The column width is determined by the widest child in that column.

Layout Algorithm ​

WrapPanelWidget lays out children in a single pass:

  1. Measure: Each child is measured with unbounded extent in the wrap direction (height for horizontal, width for vertical) and the panel's max extent in the primary direction
  2. Place: Children are placed sequentially. When a child would exceed the primary extent, a new row/column is started
  3. Row/Column sizing: Each row's height (or column's width) is the maximum of its children's measured sizes

Sizing Children ​

WrapPanel children should generally use fixed or content sizing:

csharp
ctx.WrapPanel(w => items.Select(item =>
    w.Border(b => [
        b.Text(item.Name)
    ]).FixedWidth(20).FixedHeight(5)
).ToArray())

Content-Sized Items ​

csharp
ctx.WrapPanel(w => tags.Select(tag =>
    w.Text($" {tag} ")
).ToArray())

WARNING

Avoid using .Fill() on WrapPanel children. Fill sizing requires a known container extent, but WrapPanel measures children with unbounded constraints in the wrap direction, which can produce unexpected results.

Focus Navigation ​

WrapPanelWidget provides default keyboard bindings:

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

Focus order follows the sequential order of children in the array, not the visual row/column position.

Clipping ​

Content that extends beyond the WrapPanel's bounds is clipped by default. For scrollable wrap layouts, wrap the WrapPanel in a VScrollPanel:

csharp
ctx.VScrollPanel(
    ctx.WrapPanel(w => items.Select(item =>
        w.Border(b => [
            b.Text(item.Name),
            b.Text(item.Description)
        ]).FixedWidth(25)
    ).ToArray())
)

Common Patterns ​

Card Grid ​

Display a collection of items in a responsive card layout:

csharp
ctx.WrapPanel(w => templates.Select(t =>
    w.Border(b => [
        b.Text(t.Name),
        b.Text(t.Language),
        b.Text($"⭐ {t.Stars}")
    ]).FixedWidth(30)
).ToArray())

Tag Cloud ​

Arrange tags or labels that wrap naturally:

csharp
ctx.WrapPanel(w => tags.Select(tag =>
    w.Text($" [{tag}] ")
).ToArray())

Toolbar with Overflow ​

Create a toolbar that wraps buttons to the next row when the terminal is narrow:

csharp
ctx.WrapPanel(w => [
    w.Button("New").OnClick(_ => New()),
    w.Button("Open").OnClick(_ => Open()),
    w.Button("Save").OnClick(_ => Save()),
    w.Button("Export").OnClick(_ => Export()),
    w.Button("Settings").OnClick(_ => Settings()),
])

Performance Considerations ​

  • WrapPanel measures all children during layout
  • Children are measured with unbounded constraints in the wrap direction
  • For large collections, consider virtualizing with ScrollPanel or paginating manually
  • Fixed-size children are faster to lay out than content-sized children
  • HStackWidget — For single-row horizontal layouts without wrapping
  • VStackWidget — For single-column vertical layouts without wrapping
  • GridWidget — For explicit two-dimensional grid positioning
  • Scroll — For scrollable content when WrapPanel overflows

Released under the MIT License.