Skip to content

indaco/herald

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

herald

HTML-inspired typography for terminal UIs in Go.

CI Code coverage Go Report Card Security Scan version Go Reference License Built with Devbox

Quick Start | Elements | Composition | Customization | Themes | Ecosystem | Examples

herald maps familiar HTML elements (H1-H6, P, Blockquote, UL, OL, Code, HR, Tables, Alerts, and inline styles) to styled terminal output, built on lipgloss v2.

It ships with a Rose Pine-inspired default theme, built-in themes matching the Charm ecosystem (Dracula, Catppuccin, Base16, Charm), and full style customization via functional options and ColorPalette.

Works with any CLI or TUI - and if you use huh or other Charm-based libraries, the built-in themes pair seamlessly with theirs out of the box.

herald demo output

Default Rose Pine theme (dark and light). herald also ships with Dracula, Catppuccin, Base16, and Charm themes.

Installation

go get github.com/indaco/herald@latest

Requires Go 1.25 or later.

Quick Start

package main

import (
    "fmt"
    "github.com/indaco/herald"
)

func main() {
    ty := herald.New()

    fmt.Println(ty.H1("Getting Started"))
    fmt.Println(ty.P("herald renders terminal typography using lipgloss styles."))
    fmt.Println(ty.UL("Headings", "Block elements", "Inline styles"))
}

Available elements

Headings

Preview

headings demo

H1-H3 render with a repeated underline character beneath the text. H4-H6 render with a left bar prefix.

Method Decoration Default character
H1(text) underline
H2(text) underline
H3(text) underline ·
H4(text) bar prefix
H5(text) bar prefix
H6(text) bar prefix
fmt.Println(ty.H1("Main Title"))
fmt.Println(ty.H2("Section"))
fmt.Println(ty.H4("Subsection"))

Block elements

Preview

blocks demo

Method Description
P(text) Paragraph
Blockquote(text) Indented block with a left bar; supports multi-line input
CodeBlock(text, lang) Fenced code block with padding; optional line numbers and syntax highlighting
HR() Horizontal rule, configurable width and character
HRWithLabel(label) Horizontal rule with a centered label, e.g. ── Section ──
DL(pairs) Definition list from [][2]string pairs (term, description)
DT(text) Definition term (standalone)
DD(text) Definition description (standalone)
KV(key, value) Key-value pair rendered as key: value with independent styling
KVGroup(pairs) Aligned key-value list from [][2]string pairs; keys are right-padded
Address(text) Contact/author block; renders multi-line text in a distinctive italic style
AddressCard(text) Bordered card variant of Address with rounded border
FootnoteRef(n) Inline footnote reference marker, e.g. [1]
FootnoteSection(notes) Numbered footnote list with divider; returns "" if notes is empty
fmt.Println(ty.Blockquote("First line.\nSecond line."))

fmt.Println(ty.CodeBlock("func main() {\n\tfmt.Println(\"hello\")\n}"))
fmt.Println(ty.HR())
fmt.Println(ty.HRWithLabel("Section"))

fmt.Println(ty.DL([][2]string{
    {"Go", "A statically typed, compiled language"},
    {"Rust", "A systems programming language"},
}))

// Standalone terms and descriptions
fmt.Println(ty.DT("Go"))
fmt.Println(ty.DD("A statically typed, compiled language"))

// Key-value pairs
fmt.Println(ty.KV("Name", "Alice"))

fmt.Println(ty.KVGroup([][2]string{
    {"Name", "Alice"},
    {"Role", "Admin"},
    {"Status", "Active"},
}))

fmt.Println(ty.Address("Jane Doe\njane@example.com\nSan Francisco, CA"))

// Footnotes compose with paragraphs via string concatenation
fmt.Println(ty.P("herald supports rich typography" + ty.FootnoteRef(1) + " with multiple elements" + ty.FootnoteRef(2)))
fmt.Println(ty.FootnoteSection([]string{
    "Built on lipgloss v2",
    "Headings, lists, alerts, and more",
}))

Inline styles

Preview

inline styles demo

Method Renders as
Code(text, lang) Inline code with background highlight; lang is optional, used when a CodeFormatter is set
Bold(text) Bold
Italic(text) Italic
Underline(text) Underlined
Strikethrough(text) Strikethrough
Small(text) Faint
Mark(text) Highlighted background
Link(label, url) Styled link; url is optional - when both differ, renders as label (url)
Kbd(text) Keyboard key indicator
Abbr(abbr, desc) Abbreviation; desc is optional, appended in parentheses
Sub(text) Renders with _ prefix (style not configurable via options)
Sup(text) Renders with ^ prefix (style not configurable via options)
Ins(text) Inserted text, prefixed with +
Del(text) Deleted text, prefixed with -, strikethrough
Badge(text) Styled pill/tag label (e.g. [SUCCESS], [BETA])
BadgeWithStyle(text, style) Badge with a one-off style override
Tag(text) Subtle pill/category label (lighter variant of Badge)
TagWithStyle(text, style) Tag with a one-off style override
SuccessBadge(text) Badge using the theme's success color (green)
WarningBadge(text) Badge using the theme's warning color (amber)
ErrorBadge(text) Badge using the theme's error color (red)
InfoBadge(text) Badge using the theme's info color (blue)
SuccessTag(text) Tag using the theme's success color
WarningTag(text) Tag using the theme's warning color
ErrorTag(text) Tag using the theme's error color
InfoTag(text) Tag using the theme's info color
fmt.Println(ty.Bold("important") + " and " + ty.Italic("nuanced"))
fmt.Println(ty.Kbd("Ctrl") + " + " + ty.Kbd("C"))
fmt.Println(ty.Link("Go website", "https://go.dev"))
fmt.Println(ty.Abbr("CSS", "Cascading Style Sheets"))
fmt.Println(ty.Sub("2") + "O" + ty.Sup("n"))
fmt.Println(ty.Ins("added line"))
fmt.Println(ty.Del("removed line"))
fmt.Println(ty.Badge("SUCCESS") + " " + ty.Badge("BETA"))
fmt.Println(ty.Tag("v2.0") + " " + ty.Tag("go"))

// Semantic variants use the theme's status colors automatically
fmt.Println(ty.SuccessBadge("running"), ty.ErrorBadge("failed"), ty.WarningBadge("expiring"), ty.InfoBadge("pending"))
fmt.Println(ty.SuccessTag("healthy"), ty.ErrorTag("critical"), ty.WarningTag("degraded"), ty.InfoTag("maintenance"))
important and nuanced
[Ctrl] + [C]
Go website (https://go.dev)
CSS (Cascading Style Sheets)
_2O^n
+added line
-removed line
SUCCESS  BETA
v2.0  go

In a color terminal, Badge renders with a filled background pill and Tag with a lighter variant.

Lists

Preview

lists demo

Method Description
UL(items...) Unordered list with bullet character (default )
OL(items...) Ordered list with 1., 2., 3. markers
NestUL(items...) Nested unordered list with bullet cycling
NestOL(items...) Nested ordered list with optional hierarchical numbering
fmt.Println(ty.UL("Apples", "Bananas", "Cherries"))
fmt.Println(ty.OL("First", "Second", "Third"))

Nested lists

NestUL and NestOL render hierarchical lists with configurable indentation, bullet cycling, and mixed nesting.

Constructors:

Function Description
Item(text) Leaf item (no children)
Items(texts...) Batch-convert strings to leaf items
ItemWithChildren(text, children...) Item with unordered sub-list
ItemWithOLChildren(text, children...) Item with ordered sub-list
// Nested unordered list with mixed sub-lists
fmt.Println(ty.NestUL(
    herald.Item("Fruits"),
    herald.ItemWithChildren("Vegetables",
        herald.Item("Carrots"),
        herald.Item("Peas"),
    ),
    herald.ItemWithOLChildren("Ranked Desserts",
        herald.Item("Ice cream"),
        herald.Item("Cake"),
    ),
))
• Fruits
• Vegetables
  ◦ Carrots
  ◦ Peas
• Ranked Desserts
  1. Ice cream
  2. Cake
// Nested ordered list
fmt.Println(ty.NestOL(
    herald.Item("Introduction"),
    herald.ItemWithOLChildren("Main Topics",
        herald.Item("Architecture"),
        herald.Item("Design"),
    ),
    herald.Item("Conclusion"),
))
1. Introduction
2. Main Topics
  1. Architecture
  2. Design
3. Conclusion

Enable WithHierarchicalNumbers(true) for outline-style numbering (2.1., 2.2.):

ty := herald.New(herald.WithHierarchicalNumbers(true))

fmt.Println(ty.NestOL(
    herald.Item("Introduction"),
    herald.ItemWithOLChildren("Main Topics",
        herald.Item("Architecture"),
        herald.Item("Design"),
    ),
    herald.Item("Conclusion"),
))
1. Introduction
2. Main Topics
  2.1. Architecture
  2.2. Design
3. Conclusion

Tables

Preview

tables demo

Table renders a table from a [][]string slice. The first row is treated as the header. Two border presets are available: BoxBorderSet() (default, full Unicode box-drawing) and MinimalBorderSet() (header and column separators only, no outer border).

fmt.Println(ty.Table([][]string{
    {"Name", "Role", "Status"},
    {"Alice", "Admin", "Active"},
    {"Bob", "Editor", "Idle"},
}))

Bordered (default):

┌───────┬────────┬────────┐
│ Name  │ Role   │ Status │
├───────┼────────┼────────┤
│ Alice │ Admin  │ Active │
│ Bob   │ Editor │ Idle   │
└───────┴────────┴────────┘

Minimal:

ty := herald.New(herald.WithTableBorderSet(herald.MinimalBorderSet()))
 Name  │ Role   │ Status
───────┼────────┼────────
 Alice │ Admin  │ Active
 Bob   │ Editor │ Idle

TableWithOpts(rows [][]string, opts ...TableOption) accepts per-table options for column alignment, row separators, striped rows, captions, and footer rows:

// Column alignment, footer row, and caption
fmt.Println(ty.TableWithOpts([][]string{
    {"Item", "Qty", "Price"},
    {"Widget", "10", "$9.99"},
    {"Gadget", "5", "$24.50"},
    {"Total", "15", "$34.49"},
},
    herald.WithCaption("Order Summary"),
    herald.WithFooterRow(true),
    herald.WithColumnAlign(1, herald.AlignRight),
    herald.WithColumnAlign(2, herald.AlignRight),
    // Or set all column alignments at once
    // herald.WithColumnAligns(herald.AlignLeft, herald.AlignRight, herald.AlignRight),
))
// Truncate long cell content
ty.TableWithOpts(rows,
    herald.WithMaxColumnWidth(15),
)
Table option Description
WithColumnAlign(col, align) Set alignment for a column (AlignLeft/Center/Right)
WithColumnAligns(aligns...) Set alignments for all columns positionally
WithRowSeparators(true) Horizontal lines between body rows
WithStripedRows(true) Alternating row background for readability
WithCaption(text) Caption above the table
WithCaptionBottom(text) Caption below the table
WithFooterRow(true) Treat last row as a styled footer with separator
WithMaxColumnWidth(n) Truncate all columns to n chars with
WithColumnMaxWidth(col, n) Truncate a specific column (overrides global max)

Alerts

Preview

alerts demo

GitHub-style alert callouts with colored bars, icons, and labels. Five types are supported: Note, Tip, Important, Warning, and Caution.

Method Icon Color Description
Note(text) Blue Useful information for the reader
Tip(text) Green Helpful advice
Important(text) Purple Key information
Warning(text) Amber Urgent attention needed
Caution(text) Red Risk or negative outcomes
fmt.Println(ty.Note("Useful information that users should know."))
fmt.Println(ty.Tip("Helpful advice for doing things better."))
fmt.Println(ty.Important("Key information users need to know."))
fmt.Println(ty.Warning("Urgent info that needs immediate attention."))
fmt.Println(ty.Caution("Advises about risks or negative outcomes."))
│ ○ Note
│ Useful information that users should know.

│ ⚠ Warning
│ Urgent info that needs immediate attention.

See examples/002_alerts/ for the full output of all five alert types.

You can also use the generic Alert method with an AlertType:

fmt.Println(ty.Alert(herald.AlertNote, "Generic alert call."))

Composition patterns

herald provides typography primitives - you compose them for higher-level patterns. Here are some common recipes.

Status messages

Combine inline styles for colored status output:

ty := herald.New()

// Success / error with Ins/Del
fmt.Println(ty.Ins("Build completed successfully"))  // green, prefixed with +
fmt.Println(ty.Del("3 tests failed"))                // red, prefixed with -

// Semantic badges use the theme's status colors automatically
fmt.Println(ty.SuccessBadge("PASS") + " " + "All checks passed")
fmt.Println(ty.ErrorBadge("FAIL") + " " + "Linter found 2 issues")
fmt.Println(ty.WarningBadge("EXPIRING") + " " + "Certificate expires in 7 days")
fmt.Println(ty.InfoBadge("PENDING") + " " + "Deployment queued")

// Semantic tags for subtle status labels
fmt.Println(ty.SuccessTag("healthy") + " " + ty.Tag("v2.1.0"))

// Generic Badge/BadgeWithStyle for non-semantic cases
fmt.Println(ty.Badge("BETA") + " " + ty.Tag("go"))

Annotated sections

Pair headings with alerts for contextual guidance:

fmt.Println(ty.H2("Database Migration"))
fmt.Println(ty.Warning("Back up your database before proceeding."))
fmt.Println(ty.P("Run the following command to apply migrations:"))
fmt.Println(ty.CodeBlock("go run ./cmd/migrate up"))

Author blocks in release notes

Use AddressCard for styled contact information:

fmt.Println(ty.H2("Release v2.0"))
fmt.Println(ty.P("Major performance improvements and new API surface."))
fmt.Println(ty.AddressCard("Maintained by\nJane Doe\njane@example.com"))

Rich paragraphs with references

Compose inline elements and footnotes within paragraphs:

fmt.Println(ty.P(
    "herald" + ty.FootnoteRef(1) + " is built on " +
    ty.Link("lipgloss", "https://github.com/charmbracelet/lipgloss") +
    " and supports " + ty.Bold("rich text") + ", " +
    ty.Code("inline code") + ", and " + ty.Kbd("Ctrl") + "+" + ty.Kbd("C") +
    " key indicators.",
))
fmt.Println(ty.FootnoteSection([]string{"A Go library for TUI typography"}))

Global padding and framing

herald renders typography elements. Layout concerns - padding, centering, and framing - belong at the output boundary using lipgloss directly. This avoids double-wrapping when composing inline elements inside block elements.

Per-element wrapping - apply a frame style to each rendered line:

ty := herald.New()
frame := lipgloss.NewStyle().Padding(0, 2)

fmt.Println(frame.Render(ty.H1("Title")))
fmt.Println(frame.Render(ty.P("Body text with " + ty.Bold("bold"))))

Whole-output wrapping - build all output first, then wrap once:

var buf strings.Builder
buf.WriteString(ty.H1("Title") + "\n")
buf.WriteString(ty.P("Body text") + "\n")
buf.WriteString(ty.HR() + "\n")
fmt.Println(frame.Render(buf.String()))

Customization

Functional options

Pass options to herald.New() to override individual styles or tokens.

ty := herald.New(
    herald.WithHRWidth(60),
    herald.WithBulletChar("-"),
    herald.WithH1Style(
        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FF0000")),
    ),
)

Style options - each accepts a lipgloss.Style:

Headings

Option Targets
WithH1Style - WithH6Style Heading levels 1-6

Blocks

Option Targets
WithParagraphStyle P
WithBlockquoteStyle Blockquote text
WithBlockquoteBarStyle Blockquote bar
WithCodeInlineStyle Code
WithCodeBlockStyle CodeBlock
WithHRStyle HR
WithHRLabelStyle HRWithLabel

Inline

Option Targets
WithBoldStyle Bold
WithItalicStyle Italic
WithUnderlineStyle Underline
WithStrikethroughStyle Strikethrough
WithSmallStyle Small
WithMarkStyle Mark
WithLinkStyle Link
WithKbdStyle Kbd
WithAbbrStyle Abbr
WithInsStyle Ins
WithDelStyle Del

Lists & definitions

Option Targets
WithListBulletStyle Bullet/number marker
WithListItemStyle List item text
WithDTStyle Definition term
WithDDStyle Definition description

Key-value

Option Targets
WithKVKeyStyle KV / KVGroup key text
WithKVValueStyle KV / KVGroup value text

Address

Option Targets
WithAddressStyle Address
WithAddressCardStyle AddressCard content
WithAddressCardBorderStyle AddressCard border

Badge & Tag

Option Targets
WithBadgeStyle Badge
WithTagStyle Tag
WithSemanticPalette(sp) All 8 semantic methods
WithSuccessBadgeStyle SuccessBadge
WithWarningBadgeStyle WarningBadge
WithErrorBadgeStyle ErrorBadge
WithInfoBadgeStyle InfoBadge
WithSuccessTagStyle SuccessTag
WithWarningTagStyle WarningTag
WithErrorTagStyle ErrorTag
WithInfoTagStyle InfoTag

Footnotes

Option Targets
WithFootnoteRefStyle FootnoteRef
WithFootnoteItemStyle FootnoteSection items
WithFootnoteDividerStyle FootnoteSection divider

Tables

Option Targets
WithTableHeaderStyle Table header cells
WithTableCellStyle Table body cells
WithTableStripedCellStyle Alternating body rows
WithTableFooterStyle Table footer row
WithTableCaptionStyle Table caption text
WithTableBorderStyle Table border characters

Alerts

Option Targets
WithAlertStyle(type, style) Alert of given type

Callbacks

Option Targets
WithCodeFormatter(fn) Syntax-highlighting callback for Code and CodeBlock

Token options - each accepts a string, int, or bool:

Heading tokens

Option Default Description
WithH1UnderlineChar(c) Underline character for H1
WithH2UnderlineChar(c) Underline character for H2
WithH3UnderlineChar(c) · Underline character for H3
WithHeadingBarChar(c) Bar prefix character for H4-H6

List tokens

Option Default Description
WithBulletChar(c) Bullet character for UL
WithNestedBulletChars(chars) , , , Bullet characters cycling per depth for NestUL
WithListIndent(n) 2 Spaces per nesting level for NestUL/NestOL
WithHierarchicalNumbers(b) false Outline-style numbering for nested OL (e.g. 2.1.)

Block tokens

Option Default Description
WithHRChar(c) Character repeated for HR
WithHRWidth(w) 40 Width of HR in characters
WithBlockquoteBar(c) Left bar character for Blockquote
WithCodeLineNumbers(b) false Show line numbers in CodeBlock
WithCodeLineNumberSep(c) Separator between line numbers and code

Inline tokens

Option Default Description
WithInsPrefix(c) + Prefix for Ins (inserted text)
WithDelPrefix(c) - Prefix for Del (deleted text)

Key-value tokens

Option Default Description
WithKVSeparator(c) : Separator between key and value in KV/KVGroup

Table tokens

Option Default Description
WithTableBorderSet(bs) BoxBorderSet() Border character set (BoxBorderSet or MinimalBorderSet)
WithTableCellPad(n) 1 Spaces of padding inside each table cell

Footnote tokens

Option Default Description
WithFootnoteDividerChar(c) Character repeated for footnote section divider
WithFootnoteDividerWidth(w) 20 Width of footnote section divider

Alert tokens

Option Default Description
WithAlertBar(c) Left bar character for alerts
WithAlertIcon(type, icon) per-type Override icon for a specific alert type
WithAlertLabel(type, label) per-type Override label for a specific alert type

Code formatting

WithCodeFormatter accepts a func(code, language string) string callback. When set, Code() and CodeBlock() pass the raw text and language string to the formatter before applying the lipgloss style.

import (
    "strings"

    "github.com/alecthomas/chroma/v2/quick"
    "github.com/indaco/herald"
)

func chromaFormatter(style string) func(code, language string) string {
    return func(code, language string) string {
        var buf strings.Builder
        err := quick.Highlight(&buf, code, language, "terminal256", style)
        if err != nil {
            return code
        }
        return strings.TrimRight(buf.String(), "\n")
    }
}

ty := herald.New(
    herald.WithCodeFormatter(chromaFormatter("catppuccin-mocha")),
)

fmt.Println(ty.CodeBlock(`func main() { fmt.Println("hello") }`, "go"))

See examples/200_chroma-syntax-highlighting/ for a chroma-based example, or examples/201_tree-sitter-syntax-highlighting/ for a tree-sitter-based alternative.

Line numbers in code blocks

Enable line numbers with WithCodeLineNumbers(true). Line numbers are right-aligned, styled with CodeLineNumber (defaults to the Muted palette color), and separated from code by CodeLineNumberSep (default ). Line numbers are added after the CodeFormatter runs, so they work with syntax highlighting.

ty := herald.New(
    herald.WithCodeLineNumbers(true),
)

fmt.Println(ty.CodeBlock("func main() {\n\tfmt.Println(\"hello\")\n}", "go"))
1│ func main() {
2│     fmt.Println("hello")
3│ }

Customize the separator and style:

ty := herald.New(
    herald.WithCodeLineNumbers(true),
    herald.WithCodeLineNumberSep(":"),
    herald.WithCodeLineNumberStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))),
)

The following option controls the visual appearance of line numbers:

Option Targets
WithCodeLineNumberStyle Code block line numbers

Themes

Built-in themes

herald ships with named themes that match huh's built-in color palettes. Colors auto-adapt to light/dark terminal backgrounds using lipgloss.HasDarkBackground. See Pairing with huh for how to use matching themes across herald and huh.

Dracula theme demo
DraculaTheme()
Catppuccin theme demo
CatppuccinTheme()
Base16 theme demo
Base16Theme()
Charm theme demo
CharmTheme()
ty := herald.New(herald.WithTheme(herald.DraculaTheme()))

Color palette

ColorPalette lets you define 9 colors and derive a complete theme from them. All style fields map from this palette; token options (characters, widths) are unaffected and retain their defaults. Alert colors are handled separately via AlertPalette.

Field Maps to
Primary H1 headings
Secondary H2, list bullets, Badge background, Tag foreground
Tertiary H3, links, Ins, FootnoteRef
Accent H4, mark background
Highlight H5, Abbr, Del
Muted H6, blockquote, HR, HRLabel, sub/sup, DD, KVKey, Address, AddressCard, AddressCardBorder, FootnoteItem, FootnoteDivider, line numbers, table border, caption
Text Body text, paragraphs, list items, inline code, DT, KVValue, table cells, footer
Surface Background for Kbd, Tag, striped table rows
Base Background for inline code, code blocks; mark fg, Badge fg

Pass the palette to New() via WithPalette, or call ThemeFromPalette to construct a Theme value directly.

Use lipgloss.LightDark to define adaptive colors that automatically adjust to the terminal's background:

lightDark := lipgloss.LightDark(lipgloss.HasDarkBackground(os.Stdin, os.Stdout))

// Nord-inspired palette
palette := herald.ColorPalette{
    Primary:   lightDark(lipgloss.Color("#5E81AC"), lipgloss.Color("#88C0D0")),
    Secondary: lightDark(lipgloss.Color("#81A1C1"), lipgloss.Color("#81A1C1")),
    Tertiary:  lightDark(lipgloss.Color("#8FBCBB"), lipgloss.Color("#8FBCBB")),
    Accent:    lightDark(lipgloss.Color("#EBCB8B"), lipgloss.Color("#EBCB8B")),
    Highlight: lightDark(lipgloss.Color("#BF616A"), lipgloss.Color("#BF616A")),
    Muted:     lightDark(lipgloss.Color("#7B88A1"), lipgloss.Color("#4C566A")),
    Text:      lightDark(lipgloss.Color("#2E3440"), lipgloss.Color("#ECEFF4")),
    Surface:   lightDark(lipgloss.Color("#D8DEE9"), lipgloss.Color("#3B4252")),
    Base:      lightDark(lipgloss.Color("#ECEFF4"), lipgloss.Color("#2E3440")),
}

ty := herald.New(herald.WithPalette(palette))

Each lightDark(lightColor, darkColor) call returns a single adaptive color that picks the right variant based on the terminal background. This is the same approach used by herald's built-in themes and DefaultTheme().

Plain lipgloss.Color values (without LightDark) work too - they apply the same color regardless of terminal background.

Semantic palette

SemanticPalette defines four status colors used to derive the themed SuccessBadge, WarningBadge, ErrorBadge, InfoBadge, SuccessTag, WarningTag, ErrorTag, and InfoTag styles.

Field Semantic meaning Default derivation from ColorPalette
Success Running, passed, healthy Tertiary (green in most themes)
Warning Expiring, degraded Accent (amber in most themes)
Error Failed, critical, down Highlight (red in most themes)
Info Informational, neutral status Secondary (blue in most themes)

ThemeFromPalette automatically derives a SemanticPalette from your ColorPalette, so existing custom palettes produce valid semantic badge and tag styles without any changes.

Use WithSemanticPalette to override all four semantic colors at once:

ty := herald.New(
    herald.WithSemanticPalette(herald.SemanticPalette{
        Success: lipgloss.Color("#22c55e"),
        Warning: lipgloss.Color("#f59e0b"),
        Error:   lipgloss.Color("#ef4444"),
        Info:    lipgloss.Color("#3b82f6"),
    }),
)

Individual styles can be overridden with WithSuccessBadgeStyle, WithErrorTagStyle, and the other per-variant options listed in Badge & Tag.

Alert palette

AlertPalette lets you override the 5 alert colors independently from the main ColorPalette. By default, alert colors are derived from the semantic palette (DefaultAlertPalette maps Info->Note, Success->Tip, Warning->Warning, Error->Caution), with Important using ColorPalette.Secondary. Changing the semantic palette therefore updates alert colors too.

Use WithAlertPalette to override all 5 alert colors independently:

ty := herald.New(
    herald.WithAlertPalette(herald.AlertPalette{
        Note:      lightDark(lipgloss.Color("#0969DA"), lipgloss.Color("#58A6FF")),
        Tip:       lightDark(lipgloss.Color("#1A7F37"), lipgloss.Color("#3FB950")),
        Important: lightDark(lipgloss.Color("#8250DF"), lipgloss.Color("#D2A8FF")),
        Warning:   lightDark(lipgloss.Color("#9A6700"), lipgloss.Color("#D29922")),
        Caution:   lightDark(lipgloss.Color("#CF222E"), lipgloss.Color("#F85149")),
    }),
)

Individual alert icons and labels can also be customized:

ty := herald.New(
    herald.WithAlertIcon(herald.AlertTip, "💡"),
    herald.WithAlertLabel(herald.AlertNote, "Info"),
)

You can combine WithPalette with other options to override specific fields after the palette is applied:

ty := herald.New(
    herald.WithPalette(palette),
    herald.WithHRWidth(60),
    herald.WithBulletChar("-"),
)

Custom theme

The easiest way to customize is to start from an existing theme and modify specific fields:

custom := herald.DefaultTheme()
custom.H1 = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FF0000")).MarginBottom(1)
custom.BulletChar = "-"

ty := herald.New(herald.WithTheme(custom))

For a fully custom theme, construct a Theme struct directly:

custom := herald.Theme{
    H1:        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFFFFF")),
    H2:        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#AAAAAA")),
    Paragraph: lipgloss.NewStyle().MarginBottom(1),
    // set remaining Theme fields as needed...

    H1UnderlineChar: "=",
    H2UnderlineChar: "-",
    H3UnderlineChar: ".",
    HeadingBarChar:  ">",
    BulletChar:      "*",
    HRChar:          "-",
    HRWidth:         40,
    BlockquoteBar:   "|",
}

ty := herald.New(herald.WithTheme(custom))

Pairing with huh

herald is designed to complement huh - a form and prompt library for the terminal. Together they cover the full output story of a CLI: herald handles formatted display (instructions, section headers, results, documentation), while huh handles user input.

Since both are built on lipgloss, herald ships with themes that match huh's built-in palettes exactly. You get visual consistency across your entire CLI without any manual style coordination.

ty := herald.New(herald.WithTheme(herald.DraculaTheme()))

fmt.Println(ty.H1("Project Setup"))
fmt.Println(ty.P("Answer a few questions to scaffold your project."))

form := huh.NewForm(
    huh.NewGroup(
        huh.NewInput().Title("Project name").Value(&name),
        huh.NewSelect[string]().Title("Language").Options(...).Value(&lang),
    ),
).WithTheme(huh.ThemeDracula())
form.Run()

fmt.Println(ty.H2("Summary"))
fmt.Println(ty.DL([][2]string{
    {"Name", name},
    {"Language", lang},
}))

See examples/203_huh-form/ for a runnable example, and examples/204_huh-wizard/ for a multi-step wizard combining herald and huh.

Pairing with bubbletea

herald works inside bubbletea applications - build your content with herald, then display it in a bubbletea viewport or model. Herald handles the typography, bubbletea handles the interactivity.

func buildContent(ty *herald.Typography) string {
    var buf strings.Builder
    buf.WriteString(ty.H1("Release Notes") + "\n")
    buf.WriteString(ty.Badge("STABLE") + " " + ty.Tag("v2.0.0") + "\n\n")
    buf.WriteString(ty.HRWithLabel("Features") + "\n")
    buf.WriteString(ty.UL("New dashboard", "Dark mode support") + "\n")
    buf.WriteString(ty.Tip("Run `go get -u` to upgrade.") + "\n")
    return buf.String()
}

// Pass to a bubbles viewport for scrolling
m.viewport.SetContent(buildContent(ty))

See examples/205_bubbletea-release-viewer/ for a scrollable release notes viewer and examples/206_bubbletea-explorer/ for a sidebar + viewport explorer.

Pairing with tview

herald works with tview via tview.ANSIWriter, which translates lipgloss ANSI output into tview's internal color tags.

ty := herald.New()

textView := tview.NewTextView().
    SetDynamicColors(true).
    SetScrollable(true).
    SetWordWrap(true)

w := tview.ANSIWriter(textView)
fmt.Fprintln(w, ty.H1("Herald + tview"))
fmt.Fprintln(w, ty.P("ANSI escape sequences are converted to tview color tags."))
fmt.Fprintln(w, ty.UL("Headings", "Lists", "Alerts", "Tables"))

See examples/207_tview-explorer/ for a sidebar + content pane explorer.

Examples

Runnable examples are in the examples/ directory:

Example Description
000_default-theme All elements with the default Rose Pine theme
001_lists Flat, nested, mixed, and hierarchical lists
002_alerts GitHub-style alert callouts (Note, Tip, Important, Warning, Caution)
003_table Table rendering: bordered, minimal, alignment, striped rows, captions, and footer
004_semantic-badges Semantic badge and tag methods with default and custom SemanticPalette
100_custom-options Override styles, decoration chars, and tokens via functional options
101_custom-palette Custom adaptive theme from 9 colors using ColorPalette and LightDark
102_builtin-themes Built-in themes (Dracula, Catppuccin, Base16, Charm) matching huh
103_catppuccin-theme Build a full theme from the Catppuccin palette
200_chroma-syntax-highlighting Plug in chroma for syntax-highlighted code blocks
201_tree-sitter-syntax-highlighting Plug in tree-sitter for AST-based syntax highlighting
202_gotreesitter-syntax-highlighting Pure-Go tree-sitter highlighting via gotreesitter
203_huh-form Using herald with huh for interactive TUI forms
204_huh-wizard Multi-step project scaffolder with herald + huh
205_bubbletea-release-viewer Scrollable release notes viewer with bubbletea viewport
206_bubbletea-explorer Sidebar + scrollable content pane explorer with bubbletea
207_tview-explorer Sidebar + scrollable content pane explorer with tview

License

This project is licensed under the MIT License - see the LICENSE file for details.