1---
2name: charm-glamour
3description: "Render markdown to styled ANSI terminal output in Go with glamour v2. Use when rendering markdown programmatically in Go, glamour, terminal markdown rendering, or styled markdown output. NOT for viewing markdown files in terminal (use glow)."
4---
5
6# glamour - Terminal Markdown Rendering
7
8`charm.land/glamour/v2` renders markdown to styled ANSI output. Built on goldmark, supports GFM (tables, task lists, strikethrough), syntax highlighting via Chroma, emoji, and fully customizable stylesheets.
9
10## Quick Start
11
12```bash
13go get charm.land/glamour/v2@latest
14```
15
16### One-liner
17
18```go
19import "charm.land/glamour/v2"
20
21out, err := glamour.Render("# Hello\n\nSome **bold** text.", "dark")
22fmt.Print(out)
23```
24
25### With renderer (reusable)
26
27```go
28r, err := glamour.NewTermRenderer(
29 glamour.WithStandardStyle("dark"),
30 glamour.WithWordWrap(80),
31)
32if err != nil {
33 log.Fatal(err)
34}
35
36out, err := r.Render(markdown)
37fmt.Print(out)
38```
39
40## Core API
41
42### Package-level functions
43
44| Function | Description |
45|---|---|
46| `Render(in, stylePath string) (string, error)` | One-shot render with a style name or file path |
47| `RenderBytes(in []byte, stylePath string) ([]byte, error)` | Same but bytes in/out |
48| `RenderWithEnvironmentConfig(in string) (string, error)` | Uses `GLAMOUR_STYLE` env var, defaults to `"dark"` |
49
50### TermRenderer
51
52Created via `NewTermRenderer(options ...TermRendererOption)`. Reusable for multiple renders.
53
54**Methods:**
55
56| Method | Description |
57|---|---|
58| `Render(in string) (string, error)` | Render markdown string |
59| `RenderBytes(in []byte) ([]byte, error)` | Render markdown bytes |
60| `Write(b []byte) (int, error)` | Implements `io.Writer`, buffer markdown input |
61| `Close() error` | Flush buffered input, call before `Read` |
62| `Read(b []byte) (int, error)` | Implements `io.Reader`, read rendered output |
63
64**io.ReadWriter pattern** (streaming):
65
66```go
67r, _ := glamour.NewTermRenderer(glamour.WithWordWrap(80))
68r.Write([]byte("# Streamed\n\nContent here."))
69r.Close()
70
71rendered, _ := io.ReadAll(r)
72fmt.Print(string(rendered))
73```
74
75### Options
76
77| Option | Description |
78|---|---|
79| `WithStandardStyle(name string)` | Use a built-in style by name |
80| `WithStylePath(path string)` | Style name OR path to JSON file |
81| `WithStyles(cfg ansi.StyleConfig)` | Programmatic style struct |
82| `WithStylesFromJSONBytes(b []byte)` | Parse style from JSON bytes |
83| `WithStylesFromJSONFile(path string)` | Load style from JSON file |
84| `WithEnvironmentConfig()` | Use `GLAMOUR_STYLE` env var |
85| `WithWordWrap(width int)` | Word wrap width (default: 80) |
86| `WithTableWrap(wrap bool)` | Wrap table content (default: true). False truncates with ellipsis |
87| `WithInlineTableLinks(inline bool)` | Render links inline in tables instead of footer list |
88| `WithPreservedNewLines()` | Keep newlines instead of reflowing |
89| `WithEmoji()` | Enable `:emoji_code:` rendering |
90| `WithBaseURL(url string)` | Resolve relative URLs against this base |
91| `WithChromaFormatter(fmt string)` | Set Chroma formatter for code blocks |
92| `WithOptions(opts ...TermRendererOption)` | Combine multiple options |
93
94### Built-in styles
95
96| Constant | String | Use case |
97|---|---|---|
98| `styles.DarkStyle` | `"dark"` | Dark terminal backgrounds (default) |
99| `styles.LightStyle` | `"light"` | Light terminal backgrounds |
100| `styles.DraculaStyle` | `"dracula"` | Dracula color scheme |
101| `styles.TokyoNightStyle` | `"tokyo-night"` | Tokyo Night color scheme |
102| `styles.PinkStyle` | `"pink"` | Pink accent theme |
103| `styles.AsciiStyle` | `"ascii"` | ASCII-only, no unicode box chars |
104| `styles.NoTTYStyle` | `"notty"` | No ANSI codes at all, plain text |
105
106Each has a corresponding `StyleConfig` variable: `styles.DarkStyleConfig`, `styles.LightStyleConfig`, etc.
107
108## Common Patterns
109
110### Custom style (programmatic)
111
112Start from a built-in config and modify fields. Style fields use pointers for optional values.
113
114```go
115import (
116 "charm.land/glamour/v2"
117 "charm.land/glamour/v2/ansi"
118 "charm.land/glamour/v2/styles"
119)
120
121func boolPtr(b bool) *bool { return &b }
122func strPtr(s string) *string { return &s }
123func uintPtr(u uint) *uint { return &u }
124
125func customRenderer() (*glamour.TermRenderer, error) {
126 style := styles.DarkStyleConfig
127
128 // Custom H1: green text, no background
129 style.H1 = ansi.StyleBlock{
130 StylePrimitive: ansi.StylePrimitive{
131 Color: strPtr("34"),
132 Bold: boolPtr(true),
133 Prefix: "# ",
134 },
135 }
136
137 // Wider margins
138 style.Document.Margin = uintPtr(4)
139
140 // Custom code block theme
141 style.CodeBlock.Theme = "monokai"
142
143 return glamour.NewTermRenderer(
144 glamour.WithStyles(style),
145 glamour.WithWordWrap(100),
146 )
147}
148```
149
150### Custom style (JSON file)
151
152```json
153{
154 "document": {
155 "color": "252",
156 "margin": 2,
157 "block_prefix": "\n",
158 "block_suffix": "\n"
159 },
160 "heading": {
161 "color": "39",
162 "bold": true,
163 "block_suffix": "\n"
164 },
165 "h1": {
166 "color": "228",
167 "background_color": "63",
168 "bold": true,
169 "prefix": " ",
170 "suffix": " "
171 },
172 "h2": {
173 "prefix": "## "
174 },
175 "code_block": {
176 "theme": "dracula",
177 "margin": 2
178 },
179 "link": {
180 "color": "123",
181 "underline": true
182 },
183 "strong": {
184 "bold": true
185 },
186 "emph": {
187 "italic": true
188 }
189}
190```
191
192```go
193r, err := glamour.NewTermRenderer(
194 glamour.WithStylesFromJSONFile("./my-style.json"),
195 glamour.WithWordWrap(80),
196)
197```
198
199### StyleConfig structure reference
200
201```
202StyleConfig
203 Document, BlockQuote, Paragraph -> StyleBlock
204 List -> StyleList (StyleBlock + LevelIndent)
205 Heading, H1-H6 -> StyleBlock
206 Text, Emph, Strong, Strikethrough -> StylePrimitive
207 HorizontalRule -> StylePrimitive (use Format for custom rule)
208 Item, Enumeration -> StylePrimitive (BlockPrefix for bullet char)
209 Task -> StyleTask (Ticked/Unticked strings)
210 Link, LinkText -> StylePrimitive
211 Image, ImageText -> StylePrimitive
212 Code -> StyleBlock (inline code)
213 CodeBlock -> StyleCodeBlock (Theme + Chroma)
214 Table -> StyleTable (separators)
215 DefinitionList/Term/Description -> StyleBlock/StylePrimitive
216 HTMLBlock, HTMLSpan -> StyleBlock
217
218StyleBlock
219 Indent *uint, IndentToken *string, Margin *uint
220 + StylePrimitive (all fields below)
221
222StylePrimitive
223 Color, BackgroundColor *string // ANSI color number or hex "#RRGGBB"
224 Bold, Italic, Underline *bool
225 CrossedOut, Faint *bool
226 Inverse, Conceal, Blink *bool
227 Upper, Lower, Title *bool // text transform
228 Prefix, Suffix string // per-line prefix/suffix
229 BlockPrefix, BlockSuffix string // before/after entire block
230 Format string // Go template, e.g. link format
231```
232
233### Color downsampling with lipgloss (v2)
234
235Glamour v2 is "pure" - same input always gives same output. It does NOT auto-detect terminal color capabilities. Use lipgloss to downsample colors for the actual terminal.
236
237```go
238import (
239 "charm.land/glamour/v2"
240 "charm.land/lipgloss/v2"
241)
242
243r, _ := glamour.NewTermRenderer(glamour.WithWordWrap(80))
244out, _ := r.Render(markdown)
245
246// lipgloss detects terminal capabilities and downsamples
247lipgloss.Print(out)
248```
249
250Alternative with `colorprofile` for explicit control:
251
252```go
253import "github.com/charmbracelet/colorprofile"
254
255w := colorprofile.NewWriter(os.Stdout, os.Environ())
256fmt.Fprintf(w, "%s", out)
257```
258
259### Detect terminal background for style selection
260
261```go
262import "charm.land/lipgloss/v2"
263
264style := "dark"
265if !lipgloss.HasDarkBackground() {
266 style = "light"
267}
268r, _ := glamour.NewTermRenderer(glamour.WithStandardStyle(style))
269```
270
271### Environment-based style
272
273```bash
274export GLAMOUR_STYLE=dracula
275# or a file path:
276export GLAMOUR_STYLE=/path/to/custom.json
277```
278
279```go
280// Picks up GLAMOUR_STYLE, falls back to "dark"
281out, err := glamour.RenderWithEnvironmentConfig(markdown)
282
283// Or with a renderer:
284r, err := glamour.NewTermRenderer(glamour.WithEnvironmentConfig())
285```
286
287## Integration
288
289### Bubbletea viewport (scrollable markdown)
290
291```go
292import (
293 "charm.land/glamour/v2"
294 tea "charm.land/bubbletea/v2"
295 "charm.land/bubbles/v2/viewport"
296)
297
298type model struct {
299 viewport viewport.Model
300 content string
301}
302
303func initialModel(markdown string) model {
304 r, _ := glamour.NewTermRenderer(
305 glamour.WithStandardStyle("dark"),
306 glamour.WithWordWrap(78), // viewport width minus padding
307 )
308 rendered, _ := r.Render(markdown)
309
310 vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(24))
311 vp.SetContent(rendered)
312
313 return model{viewport: vp, content: rendered}
314}
315
316func (m model) Init() tea.Cmd {
317 return nil
318}
319
320func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
321 var cmd tea.Cmd
322 m.viewport, cmd = m.viewport.Update(msg)
323 return m, cmd
324}
325
326func (m model) View() string {
327 return m.viewport.View()
328}
329```
330
331Key point: set `WithWordWrap` to viewport width minus any horizontal padding/margin. Re-render when terminal resizes.
332
333### Re-render on resize
334
335```go
336func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
337 switch msg := msg.(type) {
338 case tea.WindowSizeMsg:
339 m.viewport.SetWidth(msg.Width)
340 m.viewport.SetHeight(msg.Height)
341
342 r, _ := glamour.NewTermRenderer(
343 glamour.WithStandardStyle("dark"),
344 glamour.WithWordWrap(msg.Width - 2),
345 )
346 rendered, _ := r.Render(m.rawMarkdown)
347 m.viewport.SetContent(rendered)
348 }
349
350 var cmd tea.Cmd
351 m.viewport, cmd = m.viewport.Update(msg)
352 return m, cmd
353}
354```
355
356### Lipgloss styled container around rendered markdown
357
358```go
359import "charm.land/lipgloss/v2"
360
361border := lipgloss.NewStyle().
362 Border(lipgloss.RoundedBorder()).
363 Padding(1, 2)
364
365r, _ := glamour.NewTermRenderer(
366 glamour.WithStandardStyle("dark"),
367 glamour.WithWordWrap(76), // account for border + padding (2 border + 4 padding = 6)
368)
369rendered, _ := r.Render(markdown)
370
371fmt.Println(border.Render(rendered))
372```
373
374## Common Mistakes
375
376**Using `WithAutoStyle()` or `WithColorProfile()`** - Removed in v2. Use `WithStandardStyle("dark")` and `lipgloss.Print()` for color handling.
377
378**Not accounting for margin/padding in word wrap** - If you wrap a glamour-rendered block in a lipgloss container with padding/border, subtract that width from the word wrap value or text will overflow.
379
380**Creating a new renderer per render when unnecessary** - `TermRenderer` is reusable. Create once, call `Render()` many times.
381
382**Using `fmt.Print` instead of `lipgloss.Print`** - If colors look wrong on some terminals, you need color downsampling. Use `lipgloss.Print(out)` instead of `fmt.Print(out)`.
383
384**Forgetting `Close()` when using Write/Read pattern** - After writing markdown via `r.Write()`, you must call `r.Close()` before reading with `r.Read()` or `io.ReadAll(r)`.
385
386**Import path still on v1** - v2 uses `charm.land/glamour/v2`, not `github.com/charmbracelet/glamour`.
387
388**Setting style fields directly instead of via pointer** - `Color`, `Bold`, `Italic`, etc. are pointer types. Use helper functions like `func boolPtr(b bool) *bool { return &b }`.
389
390**Using `WithStandardStyle` with a file path** - `WithStandardStyle` only accepts built-in style names. For file paths, use `WithStylePath` or `WithStylesFromJSONFile`.
391
392## Checklist
393
394- [ ] Import `charm.land/glamour/v2` (not the old github path)
395- [ ] Pick a style: built-in name, JSON file, or programmatic `StyleConfig`
396- [ ] Set `WithWordWrap` to match your output width minus borders/padding
397- [ ] Use `lipgloss.Print()` for proper color downsampling on real terminals
398- [ ] For bubbletea: re-render on `WindowSizeMsg` with updated wrap width
399- [ ] Handle errors from `NewTermRenderer` and `Render` (malformed styles, etc.)
400- [ ] For env-based config: use `WithEnvironmentConfig()` or `RenderWithEnvironmentConfig()`
401- [ ] Test with `"notty"` style for CI/non-terminal environments