TamboUI provides layout widgets for arranging children into structured compositions. Layout widgets live in dev.tamboui.layout.* subpackages within the tamboui-core module.

Layout Overview

Widget Type Description

Columns

Stateless

Multi-column grid layout

Grid

Stateless

CSS Grid-inspired layout with constraints and gutter

Dock

Stateless

5-region layout (top, bottom, left, right, center)

Stack

Stateless

Overlapping layers (painter’s algorithm)

Flow

Stateless

Wrap layout (flow left-to-right, wrap on overflow)

Columns

A grid layout widget that arranges children into a fixed number of columns. Children are placed into cells whose positions are determined by the ordering mode.

Columns columns = Columns.builder()
    .children(widget1, widget2, widget3, widget4)
    .columnCount(2)
    .spacing(1)
    .order(ColumnOrder.ROW_FIRST)
    .build();

columns.render(area, buffer);

Column ordering modes:

  • ColumnOrder.ROW_FIRST (default) - Items fill left-to-right, then top-to-bottom (like reading text)

  • ColumnOrder.COLUMN_FIRST - Items fill top-to-bottom, then left-to-right (like newspaper columns)

Builder options:

  • children(Widget…​) / children(List<Widget>) - The child widgets to arrange

  • columnCount(int) - Number of columns (required, defaults to 1)

  • spacing(int) - Gap between columns in cells (default: 0)

  • flex(Flex) - How remaining space is distributed (default: Flex.START)

  • order(ColumnOrder) - Child ordering mode (default: ROW_FIRST)

  • columnWidths(Constraint…​) - Per-column width constraints (default: fill() for all)

  • rowHeights(int…​) - Explicit row heights (default: equal distribution)

// Custom column widths and row-first ordering
Columns grid = Columns.builder()
    .children(headerWidget, contentWidget, sidebarWidget, footerWidget)
    .columnCount(2)
    .columnWidths(Constraint.length(20), Constraint.fill())
    .rowHeights(3, 10)
    .spacing(1)
    .build();

// Column-first ordering: fills top-to-bottom per column
Columns newspaperLayout = Columns.builder()
    .children(article1, article2, article3, article4)
    .columnCount(2)
    .order(ColumnOrder.COLUMN_FIRST)
    .build();
The widget-level Columns requires an explicit column count. For auto-detection based on child widths, use ColumnsElement in the Toolkit DSL (via the columns() factory method), which computes the column count from available width and child preferred widths.

Grid

A CSS Grid-inspired layout widget that arranges children into a grid with explicit control over grid dimensions, per-column/per-row sizing constraints, and gutter spacing. Unlike Columns (which uses ordering modes and spacing), Grid provides independent horizontal and vertical gutter, per-column width constraints, per-row height constraints, and constraint cycling when fewer constraints than grid dimensions (matching Textual behavior).

Grid supports two mutually exclusive modes:

  • Children mode: Sequential placement using children() and columnCount()

  • Area mode: Template-based placement using gridAreas() and area() (CSS grid-template-areas style)

The staged builder pattern ensures compile-time safety — you cannot mix modes.

Children Mode

Grid grid = Grid.builder()
    .children(widget1, widget2, widget3, widget4, widget5, widget6)
    .columnCount(3)
    .horizontalGutter(1)
    .verticalGutter(1)
    .build();

grid.render(area, buffer);

ChildrenBuilder options:

  • children(Widget…​) / children(List<Widget>) - The child widgets to arrange

  • columnCount(int) - Number of columns (defaults to 1)

  • rowCount(int) - Optional maximum rows (validates children fit within columns × rows)

  • horizontalGutter(int) - Gap between columns in cells (default: 0)

  • verticalGutter(int) - Gap between rows in cells (default: 0)

  • flex(Flex) - How remaining space is distributed (default: Flex.START)

  • columnConstraints(Constraint…​) - Per-column width constraints (default: fill() for all). Constraints cycle when fewer than the column count.

  • rowConstraints(Constraint…​) - Per-row height constraints. Constraints cycle when fewer than the row count.

  • rowHeights(int…​) - Explicit row heights (default: equal distribution)

// Grid with custom column widths and gutter
Grid dashboard = Grid.builder()
    .children(cpuPanel, memoryPanel, diskPanel,
              netUpPanel, netDownPanel, uptimePanel)
    .columnCount(3)
    .columnConstraints(
        Constraint.length(16),  // fixed first column
        Constraint.fill(),      // remaining columns fill
        Constraint.fill()
    )
    .horizontalGutter(2)
    .verticalGutter(1)
    .build();

// Grid with row constraints
Grid sized = Grid.builder()
    .children(header, content, sidebar, footer)
    .columnCount(2)
    .rowConstraints(Constraint.length(3), Constraint.fill())
    .build();

// Column constraint cycling: single constraint applied to all columns
Grid uniform = Grid.builder()
    .children(a, b, c, d, e, f)
    .columnCount(3)
    .columnConstraints(Constraint.length(10))  // all 3 cols get length(10)
    .build();

Area Mode (Grid Template Areas)

Area mode uses CSS grid-template-areas style templates for defining layouts where cells can span multiple rows and columns. Named areas must form contiguous rectangles — L-shapes and disconnected regions are rejected with a LayoutException.

// "Holy grail" layout with spanning regions
Grid layout = Grid.builder()
    .gridAreas("header header header",
               "nav    main   main",
               "nav    main   main",
               "footer footer footer")
    .area("header", headerWidget)
    .area("nav", navWidget)
    .area("main", mainWidget)
    .area("footer", footerWidget)
    .horizontalGutter(1)
    .verticalGutter(1)
    .build();

layout.render(area, buffer);

AreaBuilder options:

  • gridAreas(String…​) - Row templates defining named areas (use . for empty cells)

  • area(String, Widget) - Assign a widget to a named area

  • horizontalGutter(int) - Gap between columns (default: 0)

  • verticalGutter(int) - Gap between rows (default: 0)

  • flex(Flex) - How remaining space is distributed (default: Flex.START)

  • columnConstraints(Constraint…​) - Per-column width constraints

  • rowConstraints(Constraint…​) - Per-row height constraints

Template rules:

  • Each row is a space-separated list of area names

  • All rows must have the same number of columns

  • Area names must start with a letter (alphanumeric and underscores allowed)

  • Use . (dot) for empty cells

  • Named areas must form contiguous rectangles

// Dashboard with 2x2 spanning main area
Grid dashboard = Grid.builder()
    .gridAreas("A A B",
               "A A C",
               "D D D")
    .area("A", mainPanel)    // 2x2 span
    .area("B", sidePanel1)
    .area("C", sidePanel2)
    .area("D", statusBar)    // full-width span
    .horizontalGutter(1)
    .verticalGutter(1)
    .build();

// Empty cells with dot notation
Grid sparse = Grid.builder()
    .gridAreas("A . B",
               ". C .")
    .area("A", widget1)
    .area("B", widget2)
    .area("C", widget3)
    .build();
Areas without assigned widgets render as empty space. Assigning a widget to an undefined area throws LayoutException.
The widget-level Grid requires an explicit column count or grid areas template. For auto-sizing based on child count (ceil(sqrt(n)) columns) and CSS property support, use GridElement in the Toolkit DSL (via the grid() factory method).

Dock

A 5-region dock layout widget that arranges children into top, bottom, left, right, and center regions — the most common TUI application structure (header + sidebar + content + footer).

Dock dock = Dock.builder()
    .top(headerWidget)
    .bottom(statusBarWidget)
    .left(sidebarWidget)
    .right(outlineWidget)
    .center(editorWidget)
    .topHeight(Constraint.length(3))
    .bottomHeight(Constraint.length(1))
    .leftWidth(Constraint.length(20))
    .rightWidth(Constraint.length(20))
    .build();

dock.render(area, buffer);

Builder options:

  • .top(Widget) / .bottom(Widget) / .left(Widget) / .right(Widget) / .center(Widget) - Set region widgets (all optional)

  • .topHeight(Constraint) - Height constraint for the top region (default: length(1))

  • .bottomHeight(Constraint) - Height constraint for the bottom region (default: length(1))

  • .leftWidth(Constraint) - Width constraint for the left region (default: length(10))

  • .rightWidth(Constraint) - Width constraint for the right region (default: length(10))

Rendering algorithm: Top and bottom take full width, then the remaining middle area is split horizontally into left, center, and right. Omitted regions are skipped — for example, setting only center gives a full-area layout.

Stack

An overlapping layers widget where children render on top of each other using a painter’s algorithm (last child on top). Essential for dialogs, popups, floating overlays, and any scenario where UI elements need to overlap.

Stack stack = Stack.builder()
    .children(backgroundWidget, dialogWidget)
    .alignment(ContentAlignment.STRETCH)
    .build();

stack.render(area, buffer);

Builder options:

  • .children(Widget…​) / .children(List<Widget>) - The child widgets to stack

  • .alignment(ContentAlignment) - How children are positioned (default: STRETCH)

Alignment modes: TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, CENTER, CENTER_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, STRETCH.

Flow

A wrap layout widget where items flow left-to-right and wrap to the next line when exceeding the available width. Useful for tag clouds, button groups, chip lists, and similar layouts where items should wrap naturally.

Flow flow = Flow.builder()
    .item(tag1Widget, 8)
    .item(tag2Widget, 12)
    .item(tag3Widget, 6)
    .horizontalSpacing(1)
    .verticalSpacing(1)
    .build();

flow.render(area, buffer);

Builder options:

  • .item(Widget, int width) / .item(Widget, int width, int height) - Add an item with explicit size

  • .items(List<FlowItem>) - Set items from a list

  • .horizontalSpacing(int) - Gap between items on the same row (default: 0)

  • .verticalSpacing(int) - Gap between rows (default: 0)

The widget-level Flow requires explicit item widths via FlowItem since the Widget interface has no preferredWidth() method. For auto-measurement, use FlowElement in the Toolkit DSL (via the flow() factory method), which auto-measures children via Element.preferredWidth().

Using Layouts with Toolkit DSL

The Toolkit DSL provides fluent factories for all layout widgets:

import static dev.tamboui.toolkit.Toolkit.*;

// Multi-column grid (auto-detects column count from child widths)
columns(item1, item2, item3, item4, item5, item6)
    .spacing(1)

// Explicit column count with column-first ordering
columns(child1, child2, child3, child4)
    .columnCount(2)
    .columnFirst()

// CSS Grid-inspired layout with gutter and constraints
grid(item1, item2, item3, item4, item5, item6)
    .gridSize(3)
    .gutter(1)

// Grid with explicit dimensions and column constraints
grid(header, content, sidebar, footer)
    .gridSize(2, 2)
    .gridColumns(Constraint.length(20), Constraint.fill())
    .gutter(1, 0)

// Grid with template areas (CSS grid-template-areas style)
grid()
    .gridAreas("header header header",
               "nav    main   main",
               "nav    main   main",
               "footer footer footer")
    .area("header", text("Header").bold())
    .area("nav", list("Nav 1", "Nav 2"))
    .area("main", text("Main Content"))
    .area("footer", text("Footer").dim())
    .gutter(1)

// 5-region dock layout
dock()
    .top(text("Header").bold())
    .bottom(text("Footer").dim())
    .left(list("Nav 1", "Nav 2", "Nav 3"))
    .center(text("Main Content"))
    .topHeight(Constraint.length(3))
    .leftWidth(Constraint.length(20))

// Overlapping layers (last child on top)
stack(backgroundElement, dialogElement)
    .alignment(ContentAlignment.CENTER)

// Wrap layout (items flow and wrap)
flow(tag1, tag2, tag3, tag4, tag5)
    .spacing(1)
    .rowSpacing(1)

See API Levels for more details on the Toolkit DSL.