gitstack

grindlemire/go-tui code browser

12.1 KB markdown 400 lines 2026-06-06 · d6d303e raw
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
# Styling and Colors

## Overview

go-tui uses a Tailwind-inspired class system for styling. You set colors, text decorations, borders, and gradients through the `class` attribute on elements, and the code generator converts them into `tui.Element` options at compile time. For cases where classes aren't flexible enough, you can construct `Style` values in Go and pass them through attributes.

## Text Styles

Text decorations are applied through classes on any element that renders text:

```gsx
<div class="flex-col gap-1">
    <span class="font-bold">Bold text</span>
    <span class="font-dim">Dimmed text</span>
    <span class="italic">Italic text</span>
    <span class="underline">Underlined text</span>
    <span class="strikethrough">Struck-through text</span>
    <span class="reverse">Reversed foreground/background</span>
</div>
```

The full set of text style classes:

| Class | Effect |
|-------|--------|
| `font-bold` | Bold / bright |
| `font-dim` or `text-dim` | Dimmed / faint |
| `italic` | Italic |
| `underline` | Underlined |
| `strikethrough` | Struck through |
| `reverse` | Swaps foreground and background colors |

`font-dim` and `text-dim` are aliases that produce the same result.

## Text Colors

go-tui supports the standard 16 ANSI terminal colors. Each has a `text-` prefixed class:

```gsx
<div class="flex-col">
    <span class="text-red">Red</span>
    <span class="text-green">Green</span>
    <span class="text-blue">Blue</span>
    <span class="text-cyan">Cyan</span>
    <span class="text-magenta">Magenta</span>
    <span class="text-yellow">Yellow</span>
    <span class="text-white">White</span>
    <span class="text-black">Black</span>
</div>
```

Each color has a bright variant:

```gsx
<div class="flex-col">
    <span class="text-bright-red">Bright red</span>
    <span class="text-bright-green">Bright green</span>
    <span class="text-bright-blue">Bright blue</span>
    <span class="text-bright-cyan">Bright cyan</span>
    <span class="text-bright-magenta">Bright magenta</span>
    <span class="text-bright-yellow">Bright yellow</span>
    <span class="text-bright-white">Bright white</span>
    <span class="text-bright-black">Bright black (gray)</span>
</div>
```

### Arbitrary Hex Colors

For colors beyond the 16 ANSI palette, use the bracket syntax with a hex value:

```gsx
<span class="text-[#FF6B35]">Custom orange</span>
<span class="text-[#A3F]">Short-form hex (expands to #AA33FF)</span>
```

Both `#RRGGBB` (6-digit) and `#RGB` (3-digit shorthand) formats work. These produce true-color output if the terminal supports it, with automatic fallback to the nearest ANSI color on limited terminals.

## Background Colors

The same color set is available with the `bg-` prefix:

```gsx
<div class="flex-col gap-1">
    <span class="bg-red text-white p-1">Red background</span>
    <span class="bg-cyan text-black p-1">Cyan background</span>
    <span class="bg-bright-yellow text-black p-1">Bright yellow background</span>
    <span class="bg-[#2D1B69] text-white p-1">Custom purple background</span>
</div>
```

Every named color and bright variant that works with `text-` also works with `bg-`. Arbitrary hex values work the same way: `bg-[#RRGGBB]` or `bg-[#RGB]`.

## Combining Styles

Multiple classes compose. List them space-separated in the `class` attribute:

```gsx
<span class="font-bold text-cyan bg-black underline">
    Bold, cyan, on black, underlined
</span>
```

Styles on parent elements do not cascade to children. Each element's `class` applies only to that element:

```gsx
<div class="text-cyan">
    <span>This text is NOT cyan. It uses the default color</span>
    <span class="text-cyan">This text IS cyan</span>
</div>
```

If you want consistent styling across a subtree, apply the class to each element that needs it, or use a pure component to encapsulate the pattern.

## Borders

Borders wrap an element in box-drawing characters. Four styles are available:

| Class | Style | Characters |
|-------|-------|------------|
| `border-single` | Single line | `┌─┐│└─┘` |
| `border-double` | Double line | `╔═╗║╚═╝` |
| `border-rounded` | Rounded corners | `╭─╮│╰─╯` |
| `border-thick` | Heavy line | `┏━┓┃┗━┛` |

```gsx
<div class="flex gap-2">
    <div class="border-single p-1">
        <span>Single</span>
    </div>
    <div class="border-double p-1">
        <span>Double</span>
    </div>
    <div class="border-rounded p-1">
        <span>Rounded</span>
    </div>
    <div class="border-thick p-1">
        <span>Thick</span>
    </div>
</div>
```

### Border Colors

Color the border with `border-{color}`:

```gsx
<div class="border-rounded border-cyan p-1">
    <span>Cyan bordered box</span>
</div>
```

All 16 named colors and their bright variants work: `border-red`, `border-bright-green`, etc. Arbitrary hex values are supported too: `border-[#FF6B35]`.

### Border Titles

The `borderTitle` attribute draws a label centered in the top border line:

```gsx
<div class="border-rounded p-1" borderTitle=" Status ">
    <span>All systems normal</span>
</div>
```

```
╭────── Status ──────╮
│ All systems normal │
╰────────────────────╯
```

The title renders with the border's style, so border color classes and the `borderStyle` attribute color both the line and the label. Titles wider than the top edge are truncated. Wrapping the text in spaces (`" Status "`) keeps a gap between the label and the line characters.

Border titles also work with border gradients. The gradient draws first and the title overwrites the characters it covers.

## Gradients

Gradients interpolate between two colors across text, backgrounds, or borders.

### Syntax

The general form is:

```
{target}-gradient-{start}-{end}[-{direction}]
```

Where:
- **target**: `text`, `bg`, or `border`
- **start** and **end**: any named ANSI color (`red`, `cyan`, `bright-blue`, etc.)
- **direction** (optional): `-h` (horizontal, the default), `-v` (vertical), `-dd` (diagonal down), `-du` (diagonal up)

### Text Gradients

```gsx
<div class="flex-col gap-1">
    <span class="text-gradient-cyan-magenta">Horizontal gradient (default)</span>
    <span class="text-gradient-red-yellow-h">Explicit horizontal</span>
    <span class="text-gradient-green-blue-v">Vertical gradient</span>
    <span class="text-gradient-cyan-magenta-dd">Diagonal down</span>
    <span class="text-gradient-yellow-red-du">Diagonal up</span>
</div>
```

### Background Gradients

```gsx
<div class="bg-gradient-blue-cyan-h p-1">
    <span class="text-white">Blue-to-cyan background</span>
</div>
```

### Border Gradients

```gsx
<div class="border-rounded border-gradient-cyan-magenta p-1">
    <span>Gradient border</span>
</div>
```

The gradient travels around the perimeter of the border, shifting from the start color to the end color over the first half, then back again over the second half.

### Direction Reference

| Suffix | Direction | Description |
|--------|-----------|-------------|
| `-h` (or omitted) | Horizontal | Left to right |
| `-v` | Vertical | Top to bottom |
| `-dd` | Diagonal down | Top-left to bottom-right |
| `-du` | Diagonal up | Bottom-left to top-right |

## Programmatic Styling

When you need dynamic styles that depend on state or computed values, construct `Style` objects in Go and pass them through element attributes.

### Building Styles

Use `tui.NewStyle()` with chainable methods:

```go
highlight := tui.NewStyle().
    Bold().
    Foreground(tui.Cyan).
    Background(tui.Black)
```

Available methods on `Style`:

| Method | Effect |
|--------|--------|
| `Foreground(Color)` | Set text color |
| `Background(Color)` | Set background color |
| `Bold()` | Bold text |
| `Dim()` | Dimmed text |
| `Italic()` | Italic text |
| `Underline()` | Underlined text |
| `Strikethrough()` | Struck-through text |
| `Reverse()` | Swap foreground and background |
| `Blink()` | Blinking text (rarely supported by terminals) |

All methods return a new `Style`, so they chain:

```go
style := tui.NewStyle().Bold().Underline().Foreground(tui.Red)
```

### Color Constructors

Several ways to create a `Color`:

```go
// Named color variables (already Color values)
tui.Cyan
tui.BrightRed

// ANSI 256 palette index (0-255)
tui.ANSIColor(33)

// 24-bit RGB
tui.RGBColor(255, 107, 53)

// Hex string (returns Color and error)
color, err := tui.HexColor("#FF6B35")
```

The named color variables (`tui.Red`, `tui.Cyan`, `tui.BrightGreen`, etc.) are already `Color` values, so you can use them directly with `Foreground()` and `Background()`:

```go
style := tui.NewStyle().Foreground(tui.Cyan).Bold()
```

### Applying Styles to Elements

Pass styles through element attributes in `.gsx`:

```gsx
templ (s *myApp) Render() {
    <div borderStyle={tui.NewStyle().Foreground(tui.Magenta)} class="border-rounded p-1">
        <span textStyle={tui.NewStyle().Bold().Foreground(tui.Cyan)}>Dynamic style</span>
    </div>
}
```

Style attributes you can set:

| Attribute | Controls |
|-----------|----------|
| `textStyle` | Text foreground color, background, and decorations |
| `borderStyle` | Border line color and decorations |
| `background` | Element background fill |

### When to Use Programmatic Styles

Class-based styling handles most cases. Programmatic styles are useful when the style depends on runtime values, like coloring a number red when negative and green when positive, or when you need 256-palette indices or RGB colors computed at runtime.

```gsx
package main

import (
    "fmt"

    tui "github.com/grindlemire/go-tui"
)

type statusApp struct {
    value *tui.State[int]
}

func StatusApp() *statusApp {
    return &statusApp{
        value: tui.NewState(0),
    }
}

func (s *statusApp) KeyMap() tui.KeyMap {
    return tui.KeyMap{
        tui.On(tui.KeyEscape, func(ke tui.KeyEvent) { ke.App().Stop() }),
        tui.On(tui.Rune('+'), func(ke tui.KeyEvent) {
            s.value.Update(func(v int) int { return v + 1 })
        }),
        tui.On(tui.Rune('-'), func(ke tui.KeyEvent) {
            s.value.Update(func(v int) int { return v - 1 })
        }),
    }
}

func valueStyle(v int) tui.Style {
    if v > 0 {
        return tui.NewStyle().Bold().Foreground(tui.Green)
    }
    if v < 0 {
        return tui.NewStyle().Bold().Foreground(tui.Red)
    }
    return tui.NewStyle().Dim()
}

templ (s *statusApp) Render() {
    <div class="flex-col items-center justify-center h-full gap-1">
        <span textStyle={valueStyle(s.value.Get())}>{fmt.Sprintf("Value: %d", s.value.Get())}</span>
        <span class="font-dim">Press + / - to change, Esc to quit</span>
    </div>
}
```

## Color Capabilities

Terminals vary in color support. go-tui detects the current terminal's capabilities at startup and falls back to supported colors automatically.

### Detection

`tui.DetectCapabilities()` checks environment variables (`COLORTERM`, `TERM`, and terminal-specific variables like `ITERM_SESSION_ID` or `KITTY_WINDOW_ID`) to determine what the terminal supports:

```go
caps := tui.DetectCapabilities()
fmt.Println(caps.Colors)    // Color16, Color256, or ColorTrue
fmt.Println(caps.TrueColor) // true if 24-bit RGB is supported
fmt.Println(caps.Unicode)   // true if Unicode rendering is supported
```

### Color Levels

| Level | Constant | Colors |
|-------|----------|--------|
| None | `tui.ColorNone` | Monochrome |
| 16 | `tui.Color16` | Standard + bright ANSI |
| 256 | `tui.Color256` | ANSI 256 palette |
| True color | `tui.ColorTrue` | 24-bit RGB |

### Automatic Fallback

You don't need to handle color fallback yourself. When you use an RGB color (via `tui.RGBColor()` or hex classes like `text-[#FF6B35]`) on a terminal that only supports 256 or 16 colors, the framework approximates it to the nearest supported color using the `Color.ToANSI()` method. This maps RGB values to the closest entry in the ANSI 256 palette's 6x6x6 color cube and grayscale ramp.

If even ANSI colors aren't supported, colors are dropped entirely and the terminal's default foreground and background are used.

The styling examples from this guide look like this in the terminal:

![Styling and Colors screenshot](/guides/03.png)

## Next Steps

- [Layout](layout) -- Flexbox layout: direction, alignment, spacing, and sizing
- [State and Reactivity](state) -- Reactive state with `State[T]`