1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
|
# Layout
## Overview
go-tui uses a CSS flexbox-compatible layout engine. Every `<div>` is a flex container, and its children are flex items that you arrange with direction, alignment, spacing, and sizing controls. You can set all of these through Tailwind-style classes or through element attributes directly.
If you've used CSS flexbox, the mental model is the same: a container has a main axis (the direction children flow) and a cross axis (perpendicular to it). Properties like `justify-*` control the main axis, `items-*` control the cross axis, and `gap-*` adds space between children.
## Direction
Every flex container lays out its children along a main axis. The default is `Row` (horizontal, left to right). Use `flex-col` to switch to `Column` (vertical, top to bottom).
```gsx
templ DirectionDemo() {
<div class="flex-col items-center w-full gap-2 p-1">
<span class="font-bold">Row (default):</span>
<div class="flex gap-1">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-bold">Column:</span>
<div class="flex-col">
<span class="bg-magenta text-black px-1">A</span>
<span class="bg-magenta text-black px-1">B</span>
<span class="bg-magenta text-black px-1">C</span>
</div>
</div>
}
```
In the row layout, A, B, and C sit side by side. In the column layout, they stack vertically.
| Class | Direction | Description |
|-------|-----------|-------------|
| `flex` or `flex-row` | Row | Children flow left to right (default) |
| `flex-col` | Column | Children flow top to bottom |
You can also set direction via the attribute: `direction={tui.Column}`.
## Justify Content
Justify controls how children are distributed along the **main axis** (horizontal for rows, vertical for columns).
```gsx
templ JustifyDemo() {
<div class="flex-col items-center w-full gap-1 p-1">
<span class="font-dim">justify-start (default):</span>
<div class="flex justify-start border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">justify-center:</span>
<div class="flex justify-center border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">justify-end:</span>
<div class="flex justify-end border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">justify-between:</span>
<div class="flex justify-between border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">justify-around:</span>
<div class="flex justify-around border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">justify-evenly:</span>
<div class="flex justify-evenly border-single w-40">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
</div>
}
```
| Class | Behavior |
|-------|----------|
| `justify-start` | Pack children at the start (default) |
| `justify-center` | Center children along the main axis |
| `justify-end` | Pack children at the end |
| `justify-between` | Even spacing between children, no space at edges |
| `justify-around` | Even spacing around each child (half-space at edges) |
| `justify-evenly` | Equal spacing between children and at edges |
You can also use the attribute form: `justify={tui.JustifyCenter}`.
## Align Items
Align controls how children are positioned along the **cross axis** (vertical for rows, horizontal for columns).
```gsx
templ AlignDemo() {
<div class="flex-col items-center w-full gap-1 p-1">
<span class="font-dim">items-start:</span>
<div class="flex items-start gap-1 border-single h-5 w-40">
<span class="bg-cyan text-black px-1">Short</span>
<span class="bg-magenta text-black px-1">Taller\ntext</span>
</div>
<span class="font-dim">items-center:</span>
<div class="flex items-center gap-1 border-single h-5 w-40">
<span class="bg-cyan text-black px-1">Short</span>
<span class="bg-magenta text-black px-1">Taller\ntext</span>
</div>
<span class="font-dim">items-end:</span>
<div class="flex items-end gap-1 border-single h-5 w-40">
<span class="bg-cyan text-black px-1">Short</span>
<span class="bg-magenta text-black px-1">Taller\ntext</span>
</div>
<span class="font-dim">items-stretch (default):</span>
<div class="flex items-stretch gap-1 border-single h-5 w-40">
<span class="bg-cyan text-black px-1">Short</span>
<span class="bg-magenta text-black px-1">Taller\ntext</span>
</div>
</div>
}
```
| Class | Behavior |
|-------|----------|
| `items-start` | Align children to the start of the cross axis |
| `items-center` | Center children on the cross axis |
| `items-end` | Align children to the end of the cross axis |
| `items-stretch` | Stretch children to fill the cross axis (default) |
Individual children can override the container's alignment with `self-start`, `self-center`, `self-end`, or `self-stretch`.
## Gap
Gap adds uniform spacing between children along the main axis. It does not add space before the first child or after the last.
```gsx
templ GapDemo() {
<div class="flex-col items-center w-full gap-1 p-1">
<span class="font-dim">gap-0 (no gap):</span>
<div class="flex gap-0">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">gap-1:</span>
<div class="flex gap-1">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
<span class="font-dim">gap-3:</span>
<div class="flex gap-3">
<span class="bg-cyan text-black px-1">A</span>
<span class="bg-cyan text-black px-1">B</span>
<span class="bg-cyan text-black px-1">C</span>
</div>
</div>
}
```
Use `gap-N` where N is the number of character cells between children. You can also set it via the attribute: `gap={2}`.
## Flex Grow and Shrink
Grow and shrink control how children claim extra space or give it up when the container doesn't match the total size of its children.
### Growing
`grow` (or `flex-grow-1`) tells an element to expand and fill available space. The grow value is relative to siblings: if two siblings both have `grow`, they split the extra space equally. If one has `flex-grow-2` and another has `flex-grow-1`, the first gets twice as much extra space.
```gsx
templ GrowDemo() {
<div class="flex-col items-center w-full gap-1 p-1">
<span class="font-dim">Fixed sidebar + growing content:</span>
<div class="flex gap-1 w-50 border-single">
<div class="w-12 bg-magenta text-black p-1">
<span>Sidebar</span>
</div>
<div class="grow bg-cyan text-black p-1">
<span>Content (fills remaining space)</span>
</div>
</div>
<span class="font-dim">Two panels, equal grow:</span>
<div class="flex gap-1 w-50 border-single">
<div class="grow bg-cyan text-black p-1">
<span>Left</span>
</div>
<div class="grow bg-magenta text-black p-1">
<span>Right</span>
</div>
</div>
</div>
}
```
### Shrinking
By default, flex items can shrink below their natural size when the container is too small (`flex-shrink` defaults to 1). Use `shrink-0` to prevent an element from shrinking, or `flex-shrink-N` to set a relative shrink factor.
### Shorthand Classes
| Class | Effect |
|-------|--------|
| `grow` | `flex-grow: 1` — expand to fill space |
| `grow-0` | `flex-grow: 0` — don't grow |
| `shrink` | `flex-shrink: 1` — allow shrinking |
| `shrink-0` | `flex-shrink: 0` — don't shrink |
| `flex-1` | `flex-grow: 1, flex-shrink: 1` — grow and shrink equally |
| `flex-auto` | Same as `flex-1` |
| `flex-initial` | `flex-grow: 0, flex-shrink: 1` — shrink but don't grow |
| `flex-none` | `flex-grow: 0, flex-shrink: 0` — fixed size |
| `flex-grow-N` | Set grow factor to N |
| `flex-shrink-N` | Set shrink factor to N |
You can also use attributes: `flexGrow={1.5}`, `flexShrink={0}`.
## Flex Wrap
By default, children stay on a single line even if they overflow the container. Add `flex-wrap` to let items break onto new lines when they run out of room.
```gsx
templ WrapDemo() {
<div class="flex flex-wrap gap-1 w-40 border-single p-1">
<span class="bg-cyan text-black px-2 shrink-0">Alpha</span>
<span class="bg-cyan text-black px-2 shrink-0">Bravo</span>
<span class="bg-cyan text-black px-2 shrink-0">Charlie</span>
<span class="bg-cyan text-black px-2 shrink-0">Delta</span>
<span class="bg-cyan text-black px-2 shrink-0">Echo</span>
</div>
}
```
Items that don't fit on the first line wrap to the next. Each line handles grow, shrink, and justify on its own.
| Class | Behavior |
|-------|----------|
| `flex-wrap` | Items wrap to new lines when they overflow |
| `flex-wrap-reverse` | Items wrap in reverse order (last line appears first) |
| `flex-nowrap` | Items stay on one line (default) |
You can also use the attribute form: `flexWrap={tui.Wrap}`.
### Align Content
When wrapping produces multiple lines, `align-content` controls how the lines are spaced along the cross axis. It requires at least two lines and some free cross-axis space to have a visible effect.
```gsx
templ AlignContentDemo() {
<div class="flex flex-wrap gap-1 h-20 w-40 border-single content-center">
<span class="bg-cyan text-black px-2 shrink-0">A</span>
<span class="bg-cyan text-black px-2 shrink-0">B</span>
<span class="bg-cyan text-black px-2 shrink-0">C</span>
<span class="bg-cyan text-black px-2 shrink-0">D</span>
</div>
}
```
| Class | Behavior |
|-------|----------|
| `content-start` | Pack lines at the start of the cross axis (default) |
| `content-end` | Pack lines at the end |
| `content-center` | Center lines in the cross axis |
| `content-stretch` | Stretch lines to fill the cross axis |
| `content-between` | First line at start, last line at end, even spacing between |
| `content-around` | Equal spacing around each line |
You can also use the attribute form: `alignContent={tui.ContentCenter}`.
## Sizing
By default, elements size to their content (`Auto`). You can set explicit sizes in character cells, percentages, or keep the default auto behavior.
### Fixed Sizes
```gsx
<div class="w-30 h-10 border-rounded p-1">
<span>30 characters wide, 10 rows tall</span>
</div>
```
### Percentage Sizes
```gsx
<div class="flex gap-1 w-full">
<div class="w-1/3 bg-cyan text-black p-1">
<span>1/3 width</span>
</div>
<div class="w-2/3 bg-magenta text-black p-1">
<span>2/3 width</span>
</div>
</div>
```
### Full and Auto
```gsx
<div class="w-full h-full">
// Takes all available width and height
<div class="w-auto h-auto">
// Sizes to its content (the default)
</div>
</div>
```
### Min and Max Constraints
```gsx
<div class="grow min-w-20 max-w-60 p-1 border-single">
<span>Grows with available space, but stays between 20 and 60 characters wide</span>
</div>
```
### Size Class Reference
| Class | Effect |
|-------|--------|
| `w-N` | Fixed width of N characters |
| `h-N` | Fixed height of N rows |
| `w-full` | 100% of parent width |
| `h-full` | 100% of parent height |
| `w-auto` | Width sizes to content (default) |
| `h-auto` | Height sizes to content (default) |
| `w-1/2` | 50% of parent width |
| `w-1/3` | 33.3% of parent width |
| `w-2/3` | 66.7% of parent width |
| `h-1/2` | 50% of parent height |
| `h-1/3` | 33.3% of parent height |
| `h-2/3` | 66.7% of parent height |
| `min-w-N` | Minimum width of N characters |
| `max-w-N` | Maximum width of N characters |
| `min-h-N` | Minimum height of N rows |
| `max-h-N` | Maximum height of N rows |
For attributes, use `width={30}`, `widthPercent={50}`, `height={10}`, `heightPercent={100}`, `minWidth={20}`, `maxWidth={60}`, `minHeight={5}`, `maxHeight={20}`.
## Padding and Margin
Padding adds space inside an element's border. Margin adds space outside it. Both are measured in character cells.
```gsx
templ SpacingDemo() {
<div class="flex justify-center w-full gap-2 p-1">
<div class="border-single p-2">
<span>2 cells of padding inside the border</span>
</div>
<div class="border-single m-2">
<span>2 cells of margin outside the border</span>
</div>
</div>
}
```
### Per-Side Control
You can set padding and margin on individual sides or axis pairs:
| Class | Sides Affected |
|-------|----------------|
| `p-N` | All four sides |
| `px-N` | Left and right |
| `py-N` | Top and bottom |
| `pt-N` | Top only |
| `pr-N` | Right only |
| `pb-N` | Bottom only |
| `pl-N` | Left only |
| `m-N` | All four sides |
| `mx-N` | Left and right |
| `my-N` | Top and bottom |
| `mt-N` | Top only |
| `mr-N` | Right only |
| `mb-N` | Bottom only |
| `ml-N` | Left only |
For more control via attributes, use `padding={2}` for uniform or set each side explicitly with the `tui.WithPaddingTRBL(top, right, bottom, left)` option in Go.
## Common Layout Patterns
### Sidebar and Main Content
```gsx
templ SidebarLayout() {
<div class="flex h-full">
<div class="w-20 border-single flex-col p-1">
<span class="font-bold">Sidebar</span>
<span>Navigation</span>
<span>Settings</span>
</div>
<div class="grow flex-col p-1">
<span class="font-bold">Content</span>
<span>The main area fills the remaining width.</span>
</div>
</div>
}
```
The sidebar has a fixed width of 20 characters. The content area grows to fill the rest of the row.
### Centered Card
```gsx
templ CenteredCard() {
<div class="flex items-center justify-center h-full">
<div class="border-rounded p-2 flex-col gap-1 w-40">
<span class="font-bold text-cyan">Welcome</span>
<hr />
<span>This card is centered both horizontally and vertically.</span>
</div>
</div>
}
```
The outer container fills the terminal (`h-full`) and centers its child on both axes with `items-center` and `justify-center`. Because the default direction is `Row`, `justify-center` centers horizontally and `items-center` centers vertically.
### Dashboard Grid
```gsx
templ Dashboard() {
<div class="flex-col h-full gap-1 p-1">
<div class="flex gap-1 grow">
<div class="grow border-rounded p-1 flex-col">
<span class="font-bold text-cyan">CPU</span>
<span>45%</span>
</div>
<div class="grow border-rounded p-1 flex-col">
<span class="font-bold text-green">Memory</span>
<span>2.1 GB</span>
</div>
<div class="grow border-rounded p-1 flex-col">
<span class="font-bold text-yellow">Disk</span>
<span>67%</span>
</div>
</div>
<div class="flex gap-1 grow">
<div class="w-2/3 border-rounded p-1 flex-col">
<span class="font-bold">Network Activity</span>
<span>Sparkline goes here</span>
</div>
<div class="grow border-rounded p-1 flex-col">
<span class="font-bold">Events</span>
<span>Log feed goes here</span>
</div>
</div>
</div>
}
```
The top row has three equally-sized panels (each with `grow`). The bottom row uses `w-2/3` for a wider panel and `grow` for the remaining one.
### Stacked Form Fields
The `<input>` elements mount against a receiver, so this layout is a struct method component rather than a pure `templ` function:
```gsx
type formLayout struct{}
func FormLayout() *formLayout {
return &formLayout{}
}
templ (f *formLayout) Render() {
<div class="flex-col gap-1 p-2 w-40">
<div class="flex-col">
<span class="font-bold">Username</span>
<input placeholder="Enter username" class="border-single" />
</div>
<div class="flex-col">
<span class="font-bold">Password</span>
<input placeholder="Enter password" class="border-single" />
</div>
<div class="flex justify-end gap-1 pt-1">
<button class="bg-cyan text-black px-2">Submit</button>
</div>
</div>
}
```
Each field is a vertical stack of label and input. The button row uses `justify-end` to push the button to the right.
### Wrapping Tag Grid
```gsx
templ FlexWrapGrid() {
<div class="flex flex-wrap gap-1 grow content-center">
for _, label := range labels {
<div class="border-rounded p-1 w-16 flex-col items-center shrink-0">
<span>{label}</span>
</div>
}
</div>
}
```
Items have a fixed `w-16` width and `shrink-0` so they won't compress to fit. When the row fills up, `flex-wrap` pushes the rest onto a new line. The `content-center` class centers those lines vertically.
The `examples/04-layout` demo lets you switch between these patterns with Tab. The dashboard view:

The flex wrap view cycles through align-content modes with arrow keys:

## Next Steps
- [State and Reactivity](state) -- Reactive state with `State[T]`
- [Components](components) -- Component patterns, composition, and lifecycle
|