Nib provides a set of layout containers that mirror SwiftUI's layout system. Every container takes a controls list of child views and arranges them according to its rules.
VStack -- Vertical Stacking¶
VStack arranges children from top to bottom. Use spacing to control the gap between children and alignment to control horizontal positioning.
import nib
nib.VStack(
controls=[
nib.Text("First"),
nib.Text("Second"),
nib.Text("Third"),
],
spacing=8,
alignment=nib.HorizontalAlignment.CENTER,
)
Alignment options for VStack:
| Value | Effect |
|---|---|
HorizontalAlignment.LEADING |
Align children to the left |
HorizontalAlignment.CENTER |
Center children (default) |
HorizontalAlignment.TRAILING |
Align children to the right |
A left-aligned card:
nib.VStack(
controls=[
nib.Text("Account", font=nib.Font.HEADLINE),
nib.Text("user@example.com", foreground_color=nib.Color.SECONDARY),
],
spacing=4,
alignment=nib.HorizontalAlignment.LEADING,
padding=16,
)
HStack -- Horizontal Stacking¶
HStack arranges children from left to right. Use alignment to control vertical positioning within the row.
nib.HStack(
controls=[
nib.SFSymbol("star.fill", foreground_color=nib.Color.YELLOW),
nib.Text("Favorites"),
],
spacing=8,
alignment=nib.VerticalAlignment.CENTER,
)
Alignment options for HStack:
| Value | Effect |
|---|---|
VerticalAlignment.TOP |
Align children to the top |
VerticalAlignment.CENTER |
Center children vertically (default) |
VerticalAlignment.BOTTOM |
Align children to the bottom |
A toolbar-style row with a centered title:
nib.HStack(
controls=[
nib.Button("Back", action=go_back),
nib.Spacer(),
nib.Text("Title", font=nib.Font.HEADLINE),
nib.Spacer(),
nib.Button("Done", action=finish),
],
spacing=8,
padding={"horizontal": 16, "vertical": 8},
)
ZStack -- Overlays¶
ZStack layers children on top of each other. The first child is at the back, and each subsequent child is drawn on top. Use alignment to position children within the stack.
nib.ZStack(
controls=[
nib.Rectangle(corner_radius=12, fill=nib.Color.BLUE),
nib.Text("Overlay Text", foreground_color=nib.Color.WHITE),
],
alignment=nib.Alignment.CENTER,
)
Alignment options for ZStack:
| Value | Position |
|---|---|
Alignment.TOP_LEADING |
Top-left corner |
Alignment.TOP |
Top center |
Alignment.TOP_TRAILING |
Top-right corner |
Alignment.LEADING |
Center-left |
Alignment.CENTER |
Dead center (default) |
Alignment.TRAILING |
Center-right |
Alignment.BOTTOM_LEADING |
Bottom-left corner |
Alignment.BOTTOM |
Bottom center |
Alignment.BOTTOM_TRAILING |
Bottom-right corner |
A notification badge over an icon:
nib.ZStack(
controls=[
nib.SFSymbol("bell.fill", font=nib.Font.TITLE),
nib.Text(
"3",
font=nib.Font.CAPTION,
foreground_color=nib.Color.WHITE,
background=nib.Circle(fill=nib.Color.RED),
padding=4,
),
],
alignment=nib.Alignment.TOP_TRAILING,
)
Nesting Stacks¶
Combine stacks to build complex layouts. This is the primary way to compose UI in Nib.
import nib
def main(app: nib.App):
app.title = "Contacts"
app.icon = nib.SFSymbol("person.2")
app.width = 300
app.height = 400
app.build(
nib.VStack(
controls=[
# Header row
nib.HStack(
controls=[
nib.Text("Contacts", font=nib.Font.TITLE),
nib.Spacer(),
nib.Button("Add", action=lambda: None),
],
padding={"horizontal": 16, "top": 16},
),
# Contact cards
nib.VStack(
controls=[
_contact_row("Alice", "alice@example.com"),
_contact_row("Bob", "bob@example.com"),
_contact_row("Charlie", "charlie@example.com"),
],
spacing=8,
padding=16,
),
],
)
)
def _contact_row(name, email):
return nib.HStack(
controls=[
nib.SFSymbol("person.circle.fill", foreground_color=nib.Color.BLUE),
nib.VStack(
controls=[
nib.Text(name, font=nib.Font.HEADLINE),
nib.Text(email, font=nib.Font.CAPTION, foreground_color=nib.Color.SECONDARY),
],
alignment=nib.HorizontalAlignment.LEADING,
spacing=2,
),
],
spacing=10,
alignment=nib.VerticalAlignment.CENTER,
)
nib.run(main)
ScrollView¶
ScrollView wraps content in a scrollable region. Set the axes parameter to control direction.
nib.ScrollView(
controls=[
nib.VStack(
controls=[nib.Text(f"Item {i}") for i in range(50)],
spacing=4,
),
],
axes="vertical", # "vertical" (default), "horizontal", or "both"
shows_indicators=True,
height=300,
)
A horizontal image gallery:
nib.ScrollView(
controls=[
nib.HStack(
controls=[
nib.Rectangle(corner_radius=8, fill=nib.Color.BLUE, width=120, height=80),
nib.Rectangle(corner_radius=8, fill=nib.Color.GREEN, width=120, height=80),
nib.Rectangle(corner_radius=8, fill=nib.Color.ORANGE, width=120, height=80),
nib.Rectangle(corner_radius=8, fill=nib.Color.PURPLE, width=120, height=80),
],
spacing=8,
),
],
axes="horizontal",
shows_indicators=False,
)
Tip
Wrap your scroll content inside a single VStack or HStack. ScrollView determines its scrollable area from the combined size of its children.
List¶
List displays rows in a native scrollable column with platform-standard styling (row separators, insets). Use it for settings screens, data tables, and any list-based UI.
nib.List(
controls=[
nib.Text("Apple"),
nib.Text("Banana"),
nib.Text("Cherry"),
],
)
Each child is rendered as a separate row. You can use any view as a row:
nib.List(
controls=[
nib.HStack(
controls=[
nib.SFSymbol("person.circle"),
nib.Text("Alice"),
nib.Spacer(),
nib.Text("Online", foreground_color=nib.Color.GREEN),
],
spacing=8,
),
nib.HStack(
controls=[
nib.SFSymbol("person.circle"),
nib.Text("Bob"),
nib.Spacer(),
nib.Text("Offline", foreground_color=nib.Color.GRAY),
],
spacing=8,
),
],
height=200,
)
Section¶
Section groups content within a List or Form, providing optional header and footer text.
nib.List(
controls=[
nib.Section(
controls=[
nib.Text("Apple"),
nib.Text("Banana"),
],
header="Fruits",
),
nib.Section(
controls=[
nib.Text("Carrot"),
nib.Text("Broccoli"),
],
header="Vegetables",
footer="Eat your greens!",
),
],
)
Note
Section is designed for use inside List or Form. Outside these containers, it may not render with the expected visual grouping.
Form¶
Form is a container for data-entry controls. On macOS it defaults to a two-column layout with labels on the left and controls on the right.
nib.Form(
controls=[
nib.Toggle("Dark Mode", is_on=False),
nib.Picker("Language", selection="English", options=["English", "Spanish", "French"]),
nib.TextField(value="", placeholder="Username"),
],
style=nib.FormStyle.COLUMNS,
)
Form styles:
| Style | Effect |
|---|---|
FormStyle.AUTOMATIC |
Platform default |
FormStyle.COLUMNS |
Two-column label/control layout (macOS default) |
FormStyle.GROUPED |
Grouped sections with visual separation |
A settings form with sections:
nib.Form(
controls=[
nib.Section(
controls=[
nib.Toggle("Push Notifications", is_on=True),
nib.Toggle("Email Notifications", is_on=False),
],
header="Notifications",
footer="Choose how you want to be notified.",
),
nib.Section(
controls=[
nib.Picker("Theme", selection="System", options=["Light", "Dark", "System"]),
nib.Slider("Font Size", value=14, min_value=10, max_value=24),
],
header="Appearance",
),
],
style=nib.FormStyle.GROUPED,
)
Spacer¶
Spacer is a flexible view that expands to fill available space in a stack. It is one of the most useful layout primitives.
# Push "Right" to the trailing edge
nib.HStack(
controls=[
nib.Text("Left"),
nib.Spacer(),
nib.Text("Right"),
],
)
Use min_length to guarantee a minimum gap:
nib.HStack(
controls=[
nib.Text("A"),
nib.Spacer(min_length=20),
nib.Text("B"),
nib.Spacer(min_length=20),
nib.Text("C"),
],
)
In a VStack, Spacer expands vertically. This pushes content to the top and bottom:
nib.VStack(
controls=[
nib.Text("Header", font=nib.Font.HEADLINE),
nib.Spacer(),
nib.Text("Footer", font=nib.Font.CAPTION),
],
height=400,
)
Divider¶
Divider draws a thin horizontal (in VStack) or vertical (in HStack) line to visually separate content.
nib.VStack(
controls=[
nib.Text("Section 1"),
nib.Divider(),
nib.Text("Section 2"),
],
spacing=8,
)
Grid and GridRow¶
Grid arranges views in a fixed two-dimensional grid with explicit rows. Each GridRow defines one row of cells.
nib.Grid(
controls=[
nib.GridRow(controls=[nib.Text("Name"), nib.Text("Score")]),
nib.GridRow(controls=[nib.Text("Alice"), nib.Text("95")]),
nib.GridRow(controls=[nib.Text("Bob"), nib.Text("87")]),
],
horizontal_spacing=20,
vertical_spacing=8,
)
Info
Grid sizes all its children eagerly. For large data sets, use LazyVGrid or LazyHGrid instead.
LazyVGrid¶
LazyVGrid creates a vertically-scrolling grid. You define the column layout with GridItem specifications, and Nib fills columns left to right, wrapping into new rows.
from nib import GridItem, GridItemSize
nib.LazyVGrid(
columns=[
GridItem(GridItemSize.FLEXIBLE),
GridItem(GridItemSize.FLEXIBLE),
GridItem(GridItemSize.FLEXIBLE),
],
controls=[
nib.Rectangle(corner_radius=8, fill=nib.Color.BLUE, height=60)
for _ in range(9)
],
spacing=10,
)
GridItem sizing strategies¶
| Strategy | Constructor | Behavior |
|---|---|---|
| Fixed | GridItem(GridItemSize.FIXED, 100) |
Exactly 100 points wide |
| Flexible | GridItem(GridItemSize.FLEXIBLE, 50) |
At least 50pt, expands to fill |
| Adaptive | GridItem(GridItemSize.ADAPTIVE, 80) |
Fits as many 80pt+ columns as possible |
Convenience constructors are also available:
from nib import fixed, flexible, adaptive
# Three fixed 100pt columns
nib.LazyVGrid(columns=[fixed(100), fixed(100), fixed(100)], controls=[...])
# As many columns as fit, each at least 80pt
nib.LazyVGrid(columns=[adaptive(80)], controls=[...])
# Two flexible columns, minimum 50pt each
nib.LazyVGrid(columns=[flexible(50), flexible(50)], controls=[...])
LazyHGrid¶
LazyHGrid is the horizontal counterpart: you define rows and children flow horizontally, wrapping into new columns.
nib.ScrollView(
controls=[
nib.LazyHGrid(
rows=[
GridItem(GridItemSize.FIXED, 50),
GridItem(GridItemSize.FIXED, 50),
],
controls=[
nib.Rectangle(corner_radius=4, fill=nib.Color.GREEN, width=80)
for _ in range(12)
],
spacing=10,
),
],
axes="horizontal",
)
Tip
Wrap a LazyHGrid in a horizontal ScrollView so the content can scroll when it exceeds the visible width.
Full Example¶
A complete app demonstrating multiple layout techniques:
import nib
def main(app: nib.App):
app.title = "Layout Demo"
app.icon = nib.SFSymbol("rectangle.3.group")
app.width = 320
app.height = 480
app.build(
nib.ScrollView(
controls=[
nib.VStack(
controls=[
# Header
nib.HStack(
controls=[
nib.Text("Dashboard", font=nib.Font.TITLE),
nib.Spacer(),
nib.SFSymbol("gear", foreground_color=nib.Color.SECONDARY),
],
),
nib.Divider(),
# Stats grid
nib.LazyVGrid(
columns=[nib.GridItem(nib.GridItemSize.FLEXIBLE), nib.GridItem(nib.GridItemSize.FLEXIBLE)],
controls=[
_stat_card("Downloads", "1,234", nib.Color.BLUE),
_stat_card("Users", "567", nib.Color.GREEN),
_stat_card("Revenue", "$8.9k", nib.Color.ORANGE),
_stat_card("Rating", "4.8", nib.Color.PURPLE),
],
spacing=8,
),
# Recent items
nib.List(
controls=[
nib.Section(
controls=[
nib.Text("v2.1 released"),
nib.Text("Bug fix deployed"),
nib.Text("New feature added"),
],
header="Recent Activity",
),
],
height=200,
),
],
spacing=12,
padding=16,
),
],
)
)
def _stat_card(title, value, color):
return nib.VStack(
controls=[
nib.Text(value, font=nib.Font.TITLE, foreground_color=color),
nib.Text(title, font=nib.Font.CAPTION, foreground_color=nib.Color.SECONDARY),
],
spacing=4,
padding=12,
background=nib.Rectangle(corner_radius=8, fill=nib.Color(hex="#1a1a1a")),
)
nib.run(main)