The Grid widget provides a powerful 2D layout system with explicit placement, spans, and auto-placement for complex UI structures.
Basic Usage
import { ui } from "@rezi-ui/core";
ui.grid({ columns: 3 }, [
ui.text("Item 1"),
ui.text("Item 2"),
ui.text("Item 3"),
ui.text("Item 4"),
ui.text("Item 5"),
ui.text("Item 6"),
]);
Props
Column definition. Either:
number - Fixed number of equal-width columns
string - Track sizes (e.g., "1fr 2fr 1fr", "auto 20 auto")
Row definition. Either:
number - Fixed number of equal-height rows
string - Track sizes (e.g., "auto 10 1fr")
Spacing between all cells (columns and rows).
Spacing between rows. Overrides gap for rows.
Spacing between columns. Overrides gap for columns.
Declarative transition settings for container animations.
Exit transition before unmount.
Column/Row Definitions
Fixed Columns
ui.grid({ columns: 3 }, [
ui.text("A"),
ui.text("B"),
ui.text("C"),
ui.text("D"),
ui.text("E"),
ui.text("F"),
]);
Creates a 3-column grid with equal-width columns.
Fractional Units (fr)
Distribute remaining space proportionally:
ui.grid({ columns: "1fr 2fr 1fr" }, [
ui.text("Narrow"), // 25% width
ui.text("Wide"), // 50% width
ui.text("Narrow"), // 25% width
]);
Fixed Sizes
Use absolute cell counts:
ui.grid({ columns: "20 1fr 20" }, [
ui.text("Left"), // 20 cells wide
ui.text("Center"), // Fills remaining
ui.text("Right"), // 20 cells wide
]);
Auto Sizing
Size to content:
ui.grid({ columns: "auto 1fr auto" }, [
ui.text("Fit"),
ui.text("Flexible"),
ui.text("Fit"),
]);
Mixed Definitions
ui.grid({ columns: "auto 30 1fr 2fr 15" }, items);
Explicit Placement
Place items at specific grid positions:
ui.grid({ columns: 4, rows: 3 }, [
ui.box({ gridColumn: 1, gridRow: 1 }, [ui.text("Top-left")]),
ui.box({ gridColumn: 4, gridRow: 1 }, [ui.text("Top-right")]),
ui.box({ gridColumn: 2, gridRow: 2, colSpan: 2 }, [ui.text("Center span")]),
ui.box({ gridColumn: 1, gridRow: 3 }, [ui.text("Bottom-left")]),
]);
Placement Props
These props are available on child widgets:
gridColumn: number - Starting column (1-based)
gridRow: number - Starting row (1-based)
colSpan: number - Number of columns to span
rowSpan: number - Number of rows to span
Spans
Column Spans
ui.grid({ columns: 4 }, [
ui.box({ colSpan: 2 }, [ui.text("Wide")]),
ui.text("A"),
ui.text("B"),
ui.box({ colSpan: 3 }, [ui.text("Extra wide")]),
ui.text("C"),
]);
Row Spans
ui.grid({ columns: 3, rows: 3 }, [
ui.box({ gridColumn: 1, gridRow: 1, rowSpan: 3 }, [ui.text("Tall")]),
ui.text("A"),
ui.text("B"),
ui.text("C"),
ui.text("D"),
ui.text("E"),
ui.text("F"),
]);
Multi-Directional Spans
ui.grid({ columns: 4, rows: 4 }, [
ui.box({ gridColumn: 2, gridRow: 2, colSpan: 2, rowSpan: 2 }, [
ui.text("Center block"),
]),
// Other items auto-placed around the span
]);
Auto-Placement
Items without explicit placement are automatically positioned:
ui.grid({ columns: 3 }, [
ui.box({ gridColumn: 2 }, [ui.text("Fixed at column 2")]),
ui.text("Auto"), // Placed at column 1
ui.text("Auto"), // Placed at column 3
ui.text("Auto"), // Placed at column 1, row 2
]);
Auto-placement respects occupied cells from explicit placement and spans.
Gap Spacing
ui.grid({ columns: 3, gap: 1 }, items);
Separate Row/Column Gaps
ui.grid({
columns: 3,
rowGap: 2, // 2 cells between rows
columnGap: 1, // 1 cell between columns
}, items);
Examples
Dashboard Layout
function dashboard(): VNode {
return ui.grid(
{
columns: "1fr 2fr 1fr",
rows: "auto 1fr auto",
gap: 1,
},
[
// Header spans all columns
ui.box({ colSpan: 3, border: "single", p: 1 }, [
ui.text("Dashboard", { variant: "heading" }),
]),
// Sidebar
ui.box({ rowSpan: 1, border: "single", p: 1 }, [
ui.text("Sidebar"),
]),
// Main content
ui.box({ border: "single", p: 1 }, [
ui.text("Main Content"),
]),
// Right panel
ui.box({ border: "single", p: 1 }, [
ui.text("Metrics"),
]),
// Footer spans all columns
ui.box({ colSpan: 3, border: "single", p: 1 }, [
ui.text("Footer"),
]),
]
);
}
Card Grid
function cardGrid(items: Array<{ title: string; content: string }>): VNode {
return ui.grid(
{
columns: 4,
gap: 2,
},
items.map((item, i) =>
ui.box({ border: "rounded", p: 1, key: String(i) }, [
ui.text(item.title, { style: { bold: true } }),
ui.text(item.content, { style: { dim: true } }),
])
)
);
}
Asymmetric Layout
function asymmetricLayout(): VNode {
return ui.grid(
{
columns: "1fr 1fr 1fr 1fr",
rows: "auto auto",
gap: 1,
},
[
// Featured item (2x2)
ui.box({ colSpan: 2, rowSpan: 2, border: "single", p: 1 }, [
ui.text("Featured", { variant: "heading" }),
]),
// Small items
ui.box({ border: "single", p: 1 }, [ui.text("Item 1")]),
ui.box({ border: "single", p: 1 }, [ui.text("Item 2")]),
ui.box({ border: "single", p: 1 }, [ui.text("Item 3")]),
ui.box({ border: "single", p: 1 }, [ui.text("Item 4")]),
]
);
}
function formGrid(fields: Array<{
id: string;
label: string;
value: string;
fullWidth?: boolean;
}>): VNode {
return ui.grid(
{
columns: "auto 1fr",
rowGap: 1,
columnGap: 2,
},
fields.flatMap((field) => {
const input = ui.input({ id: field.id, value: field.value });
if (field.fullWidth) {
return [
ui.text(field.label, { style: { bold: true } }),
ui.box({ colSpan: 1 }, [input]),
];
}
return [
ui.text(field.label, { style: { bold: true } }),
input,
];
})
);
}
Gallery with Hero
function galleryWithHero(images: Array<{ id: string; src: Uint8Array }>): VNode {
const [hero, ...rest] = images;
return ui.grid(
{
columns: 4,
rows: "2fr 1fr 1fr",
gap: 1,
},
[
// Hero image spans 4 columns, 2 rows
hero && ui.box({ colSpan: 4, rowSpan: 2 }, [
ui.image({
id: hero.id,
src: hero.src,
width: 80,
height: 40,
fit: "cover",
}),
]),
// Thumbnails auto-placed
...rest.slice(0, 8).map((img) =>
ui.image({
id: img.id,
src: img.src,
width: 20,
height: 10,
fit: "cover",
key: img.id,
})
),
]
);
}
Metrics Grid
function metricsGrid(metrics: Array<{
label: string;
value: number;
trend: number[];
}>): VNode {
return ui.grid(
{
columns: "repeat(3, 1fr)",
gap: 2,
},
metrics.map((m) =>
ui.box({ border: "rounded", p: 1, key: m.label }, [
ui.text(m.label, { style: { dim: true } }),
ui.text(String(m.value), { variant: "heading" }),
ui.sparkline(m.trend, { width: 20 }),
])
)
);
}
function sidebarLayout(sidebar: VNode, main: VNode): VNode {
return ui.grid(
{
columns: "20 1fr",
rows: "1fr",
gap: 1,
},
[sidebar, main]
);
}
Mosaic Grid
function mosaicGrid(): VNode {
return ui.grid(
{
columns: 6,
rows: 4,
gap: 1,
},
[
// Large block
ui.box({ gridColumn: 1, gridRow: 1, colSpan: 3, rowSpan: 2, border: "single" }, [
ui.text("Large"),
]),
// Medium blocks
ui.box({ gridColumn: 4, gridRow: 1, colSpan: 3, rowSpan: 2, border: "single" }, [
ui.text("Medium"),
]),
// Small blocks
ui.box({ gridColumn: 1, gridRow: 3, colSpan: 2, rowSpan: 2, border: "single" }, [
ui.text("Small"),
]),
ui.box({ gridColumn: 3, gridRow: 3, colSpan: 2, rowSpan: 2, border: "single" }, [
ui.text("Small"),
]),
ui.box({ gridColumn: 5, gridRow: 3, colSpan: 2, rowSpan: 2, border: "single" }, [
ui.text("Small"),
]),
]
);
}
Track Sizing Reference
| Syntax | Behavior | Example |
|---|
N (number) | Fixed cell count | 20 = 20 cells |
Nfr | Fractional unit | 1fr = 1 share, 2fr = 2 shares |
auto | Size to content | auto |
| Space-separated | Multiple tracks | "1fr 2fr 1fr" |
Occupancy-Aware Auto-Placement
The grid layout engine tracks occupied cells and auto-places items in the first available slot:
ui.grid({ columns: 3 }, [
// Explicit placement reserves cells
ui.box({ gridColumn: 2, colSpan: 2 }, [ui.text("Wide")]),
// Auto-placed items avoid occupied cells
ui.text("A"), // Column 1, row 1
ui.text("B"), // Column 1, row 2 (columns 2-3 occupied)
ui.text("C"), // Column 2, row 2
]);
Responsive Patterns
While Rezi doesn’t have media queries, you can adapt grid layouts based on terminal size:
import { defineWidget, useTerminalSize } from "@rezi-ui/core";
const ResponsiveGrid = defineWidget<{ items: VNode[] }>((ctx, props) => {
const size = useTerminalSize();
const columns = size.cols > 120 ? 4 : size.cols > 80 ? 3 : 2;
return ui.grid({ columns, gap: 1 }, props.items);
});
- Layout computation is O(n) where n = number of items
- Explicit placement is instant
- Auto-placement tracks occupancy for correct positioning
See Also
- Row - Horizontal stack layout
- Column - Vertical stack layout
- Box - Container with border/padding
- Split Pane - Resizable panels