Layouts

libui-python provides several containers for arranging widgets. This chapter covers all of them.

VBox and HBox

VBox stacks children vertically, HBox horizontally. Use stretchy() to make a child fill available space:

"""VBox and HBox — vertical and horizontal stacking with stretchy."""

import libui
from libui.declarative import (
    App,
    Window,
    VBox,
    HBox,
    Label,
    Button,
    MultilineEntry,
    Separator,
    stretchy,
)


async def main():
    app = App()

    app.build(
        window=Window(
            "VBox & HBox",
            500,
            400,
            child=VBox(
                Label("Top (fixed)"),
                Separator(),
                HBox(
                    stretchy(Button("Left (stretchy)")),
                    Button("Center (fixed)"),
                    stretchy(Button("Right (stretchy)")),
                ),
                Separator(),
                stretchy(
                    MultilineEntry(
                        text="This multiline entry is stretchy — it fills the remaining space.",
                        wrapping=True,
                    )
                ),
                Label("Bottom (fixed)"),
            ),
        )
    )

    app.show()
    await app.wait()


libui.run(main())
VBox and HBox layout

Form

Form creates a two-column layout with labels on the left and controls on the right:

"""Form layout — two-column label + control pairs."""

import libui
from libui.declarative import (
    App,
    Window,
    VBox,
    Form,
    Group,
    Label,
    Entry,
    MultilineEntry,
    Combobox,
    State,
    stretchy,
)


async def main():
    app = App()
    status = State("Fill in the form below.")

    app.build(
        window=Window(
            "Form Layout",
            500,
            400,
            child=VBox(
                Label(text=status),
                stretchy(
                    Group(
                        "User Profile",
                        Form(
                            (
                                "First name:",
                                Entry(
                                    on_changed=lambda t: status.set(f"First: {t}"),
                                ),
                            ),
                            (
                                "Last name:",
                                Entry(
                                    on_changed=lambda t: status.set(f"Last: {t}"),
                                ),
                            ),
                            ("Email:", Entry()),
                            (
                                "Role:",
                                Combobox(
                                    items=["Admin", "Editor", "Viewer"],
                                    selected=0,
                                ),
                            ),
                            (
                                "Bio:",
                                MultilineEntry(wrapping=True),
                                True,
                            ),  # True = stretchy
                            padded=True,
                        ),
                    )
                ),
            ),
        )
    )

    app.show()
    await app.wait()


libui.run(main())
Form layout

Each row is a tuple: (label, widget) or (label, widget, stretchy).

Tab

Tab creates a tabbed container. Each page is a (name, widget) tuple:

"""Tab container — multiple pages in a tabbed view."""

import libui
from libui.declarative import (
    App,
    Window,
    Tab,
    VBox,
    Form,
    Label,
    Button,
    Entry,
    MultilineEntry,
    stretchy,
)


async def main():
    app = App()

    app.build(
        window=Window(
            "Tabs",
            500,
            350,
            child=Tab(
                (
                    "Profile",
                    Form(
                        ("Name:", Entry()),
                        ("Email:", Entry()),
                        padded=True,
                    ),
                ),
                (
                    "Settings",
                    VBox(
                        Label("Application settings go here."),
                        Button("Reset to Defaults"),
                    ),
                ),
                (
                    "Notes",
                    VBox(
                        stretchy(
                            MultilineEntry(
                                text="Write your notes here...",
                                wrapping=True,
                            )
                        ),
                    ),
                ),
            ),
        )
    )

    app.show()
    await app.wait()


libui.run(main())
Tab container

Grid

Grid places children at exact (column, row) coordinates with optional spanning — useful for keypad-style layouts and other 2D arrangements that VBox/HBox/Form cannot express:

"""Grid layout — position-based coordinate placement with GridCell.

Grid places widgets at exact (column, row) coordinates with spanning.
It is best for keypad-style layouts where you need precise 2D placement
that VBox/HBox/Form cannot express.

Note: on macOS, Grid does not expand to fill its parent width —
use Form for label + control layouts that need to stretch.
"""

import libui
from libui.declarative import App, Window, VBox, Grid, GridCell, Button, Entry, State


async def main():
    app = App()
    display = State("0")

    def press(ch):
        cur = display.value
        if cur == "0" and ch not in (".", "0"):
            display.set(ch)
        else:
            display.set(cur + ch)

    def clear():
        display.set("0")

    def btn(text, left, top, xspan=1):
        return GridCell(
            Button(text, on_clicked=lambda t=text: press(t)),
            left,
            top,
            xspan=xspan,
        )

    keypad = Grid(
        # Row 0: display spanning 4 columns
        GridCell(Entry(text=display, read_only=True), 0, 0, xspan=4),
        # Row 1
        btn("7", 0, 1),
        btn("8", 1, 1),
        btn("9", 2, 1),
        GridCell(Button("C", on_clicked=clear), 3, 1),
        # Row 2
        btn("4", 0, 2),
        btn("5", 1, 2),
        btn("6", 2, 2),
        btn("+", 3, 2),
        # Row 3
        btn("1", 0, 3),
        btn("2", 1, 3),
        btn("3", 2, 3),
        btn("-", 3, 3),
        # Row 4: zero spans 2 columns
        btn("0", 0, 4, xspan=2),
        btn(".", 2, 4),
        btn("=", 3, 4),
        padded=True,
    )

    app.build(
        window=Window(
            "Grid Layout — Calculator",
            300,
            250,
            child=VBox(keypad),
        )
    )

    app.show()
    await app.wait()


libui.run(main())
Grid layout

Key GridCell parameters:

Parameter

Description

left, top

Grid position (zero-based)

xspan, yspan

How many columns/rows to span

hexpand, vexpand

Whether to expand to fill space

halign, valign

Alignment within the cell (Align.FILL, START, CENTER, END)

Note

On macOS, Grid cells do not expand horizontally even with hexpand=True — this is a known libui-ng upstream bug in the Cocoa backend’s Auto Layout constraints. vexpand works correctly. For label + control forms that need to stretch horizontally, use Form instead. Grid works well for fixed-size coordinate layouts like keypads and toolbars.

Group

Group wraps a single child with a titled border. Combine with other containers for labeled sections:

Group("Connection Settings", child=Form(
    ("Host:", Entry()),
    ("Port:", Entry()),
), margined=True)

stretchy

stretchy(node) marks a child as expandable in VBox or HBox. Without it, children take only their natural size. Only children marked stretchy will grow to fill available space.

VBox(
    Label("Fixed size"),
    stretchy(MultilineEntry()),  # fills remaining space
    Button("Also fixed"),
)