{"id":47922828,"url":"https://github.com/indaco/herald","last_synced_at":"2026-04-04T06:19:07.017Z","repository":{"id":345157677,"uuid":"1184745826","full_name":"indaco/herald","owner":"indaco","description":"HTML-inspired typography for terminal UIs in Go","archived":false,"fork":false,"pushed_at":"2026-03-25T06:22:06.000Z","size":591,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T10:50:57.095Z","etag":null,"topics":["charmbracelet","cli","go","golang","lipgloss","styled-text","terminal","terminal-ui","tui","typography"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/indaco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-17T22:28:56.000Z","updated_at":"2026-03-25T06:22:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/indaco/herald","commit_stats":null,"previous_names":["indaco/herald"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/indaco/herald","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fherald","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fherald/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fherald/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fherald/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/indaco","download_url":"https://codeload.github.com/indaco/herald/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fherald/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31389691,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["charmbracelet","cli","go","golang","lipgloss","styled-text","terminal","terminal-ui","tui","typography"],"created_at":"2026-04-04T06:19:04.266Z","updated_at":"2026-04-04T06:19:06.381Z","avatar_url":"https://github.com/indaco.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  herald\n\u003c/h1\u003e\n\n\u003ch2 align=\"center\" style=\"font-size: 1.5rem;\"\u003e\n    HTML-inspired typography for terminal UIs in Go.\n\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/indaco/herald/actions/workflows/ci.yml\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://github.com/indaco/herald/actions/workflows/ci.yml/badge.svg\" alt=\"CI\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/indaco/herald\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/indaco/herald/branch/main/graph/badge.svg\" alt=\"Code coverage\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/report/github.com/indaco/herald\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://goreportcard.com/badge/github.com/indaco/herald\" alt=\"Go Report Card\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/indaco/herald/actions/workflows/security.yml\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://github.com/indaco/herald/actions/workflows/security.yml/badge.svg\" alt=\"Security Scan\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/indaco/herald/releases\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/v/tag/indaco/herald?label=version\u0026sort=semver\u0026color=4c1\" alt=\"version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/indaco/herald\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://pkg.go.dev/badge/github.com/indaco/herald.svg\" alt=\"Go Reference\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"LICENSE\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-mit-blue?style=flat-square\" alt=\"License\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.jetify.com/devbox\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://www.jetify.com/img/devbox/shield_moon.svg\" alt=\"Built with Devbox\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003e\u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#available-elements\"\u003eElements\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#composition-patterns\"\u003eComposition\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#customization\"\u003eCustomization\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#themes\"\u003eThemes\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#pairing-with-huh\"\u003eEcosystem\u003c/a\u003e\u003c/b\u003e |\n  \u003cb\u003e\u003ca href=\"#examples\"\u003eExamples\u003c/a\u003e\u003c/b\u003e\n\u003c/p\u003e\n\nherald maps familiar HTML elements (H1-H6, P, Blockquote, UL, OL, Code, HR, BR, Tables, Alerts, and inline styles) to styled terminal output, built on [lipgloss v2](https://github.com/charmbracelet/lipgloss).\n\nIt 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.\n\nWorks with any CLI or TUI - and if you use [huh](https://github.com/charmbracelet/huh) or other Charm-based libraries, the built-in themes pair seamlessly with theirs out of the box.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-hero.png\" alt=\"herald demo output\" width=\"600\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cem\u003eDefault Rose Pine theme (dark and light). herald also ships with Dracula, Catppuccin, Base16, and Charm themes.\u003c/em\u003e\u003c/p\u003e\n\n## Installation\n\n```sh\ngo get github.com/indaco/herald@latest\n```\n\nRequires Go 1.25 or later.\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/indaco/herald\"\n)\n\nfunc main() {\n    ty := herald.New()\n\n    fmt.Println(ty.H1(\"Getting Started\"))\n    fmt.Println(ty.P(\"herald renders terminal typography using lipgloss styles.\"))\n    fmt.Println(ty.UL(\"Headings\", \"Block elements\", \"Inline styles\"))\n}\n```\n\n\u003e [!TIP]\n\u003e **Working with Markdown input?** [herald-md](https://github.com/indaco/herald-md) is a companion module that parses Markdown (CommonMark + GFM via goldmark) and maps each element to the corresponding herald typography method - so you can render `.md` content with the same themed output, no manual wiring required.\n\u003e\n\u003e ```sh\n\u003e go get github.com/indaco/herald-md@latest\n\u003e ```\n\n\u003e [!TIP]\n\u003e **Need themed CLI help pages?** [herald-help](https://github.com/indaco/herald-help) renders `--help` output with herald's full theming. Adapters for [cobra](https://github.com/indaco/herald-help/tree/main/cobra), [urfave/cli](https://github.com/indaco/herald-help/tree/main/urfave), and [kong](https://github.com/indaco/herald-help/tree/main/kong) are available as sub-modules.\n\u003e\n\u003e ```sh\n\u003e go get github.com/indaco/herald-help@latest\n\u003e ```\n\n## Available elements\n\n### Headings\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-headings.png\" alt=\"headings demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\nH1-H3 render with a repeated underline character beneath the text. H4-H6 render with a left bar prefix.\n\n| Method     | Decoration | Default character |\n| ---------- | ---------- | ----------------- |\n| `H1(text)` | underline  | `═`               |\n| `H2(text)` | underline  | `─`               |\n| `H3(text)` | underline  | `·`               |\n| `H4(text)` | bar prefix | `▎`               |\n| `H5(text)` | bar prefix | `▎`               |\n| `H6(text)` | bar prefix | `▎`               |\n\n```go\nfmt.Println(ty.H1(\"Main Title\"))\nfmt.Println(ty.H2(\"Section\"))\nfmt.Println(ty.H4(\"Subsection\"))\n```\n\n### Block elements\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-blocks.png\" alt=\"blocks demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\n| Method                                | Description                                                                   |\n| ------------------------------------- | ----------------------------------------------------------------------------- |\n| `P(text)`                             | Paragraph                                                                     |\n| `Blockquote(text)`                    | Indented block with a left bar; supports multi-line input                     |\n| `CodeBlock(text, lang)`               | Fenced code block with padding; optional line numbers and syntax highlighting |\n| `HR()`                                | Horizontal rule, configurable width and character                             |\n| `HRWithLabel(label)`                  | Horizontal rule with a centered label, e.g. `── Section ──`                   |\n| `DL(pairs)`                           | Definition list from `[][2]string` pairs (term, description)                  |\n| `DT(text)`                            | Definition term (standalone)                                                  |\n| `DD(text)`                            | Definition description (standalone)                                           |\n| `KV(key, value)`                      | Key-value pair rendered as `key: value` with independent styling              |\n| `KVGroup(pairs)`                      | Aligned key-value list from `[][2]string` pairs; keys are right-padded        |\n| `KVGroupWithOpts(pairs, opts...)`     | Like `KVGroup` with per-call options for separator, styling, and indentation  |\n| `Address(text)`                       | Contact/author block; renders multi-line text in a distinctive italic style   |\n| `AddressCard(text)`                   | Bordered card variant of `Address` with rounded border                        |\n| `FootnoteRef(n)`                      | Inline footnote reference marker, e.g. `[1]`                                  |\n| `FootnoteSection(notes)`              | Numbered footnote list with divider; returns `\"\"` if notes is empty           |\n| `Fieldset(legend, content, width...)` | Bordered box with legend embedded in top border; auto-width or explicit       |\n| `Figure(content, caption)`            | Content with styled caption below                                             |\n| `FigureTop(content, caption)`         | Content with styled caption above                                             |\n| `BR()`                                | Line break, analogous to `\u003cbr/\u003e`                                              |\n| `Section(blocks...)`                  | Joins blocks with single newlines; keeps a heading tight against its content  |\n| `Compose(blocks...)`                  | Joins pre-rendered blocks with double newlines; empty blocks are skipped      |\n\n```go\nfmt.Println(ty.Blockquote(\"First line.\\nSecond line.\"))\n\nfmt.Println(ty.CodeBlock(\"func main() {\\n\\tfmt.Println(\\\"hello\\\")\\n}\"))\nfmt.Println(ty.HR())\nfmt.Println(ty.HRWithLabel(\"Section\"))\n\nfmt.Println(ty.DL([][2]string{\n    {\"Go\", \"A statically typed, compiled language\"},\n    {\"Rust\", \"A systems programming language\"},\n}))\n\n// Standalone terms and descriptions\nfmt.Println(ty.DT(\"Go\"))\nfmt.Println(ty.DD(\"A statically typed, compiled language\"))\n\n// Key-value pairs\nfmt.Println(ty.KV(\"Name\", \"Alice\"))\n\nfmt.Println(ty.KVGroup([][2]string{\n    {\"Name\", \"Alice\"},\n    {\"Role\", \"Admin\"},\n    {\"Status\", \"Active\"},\n}))\n\n// KVGroup with per-call options: no separator, pre-styled keys, indented\nfmt.Println(ty.KVGroupWithOpts([][2]string{\n    {ty.Var(\"--output\"), \"Output destination\"},\n    {ty.Var(\"--verbose\"), \"Enable verbose output\"},\n}, herald.WithKVGroupSeparator(\"\"), herald.WithKVRawKeys(true), herald.WithKVIndent(2)))\n\nfmt.Println(ty.Address(\"Jane Doe\\njane@example.com\\nSan Francisco, CA\"))\n\n// Footnotes compose with paragraphs via string concatenation\nfmt.Println(ty.P(\"herald supports rich typography\" + ty.FootnoteRef(1) + \" with multiple elements\" + ty.FootnoteRef(2)))\nfmt.Println(ty.FootnoteSection([]string{\n    \"Built on lipgloss v2\",\n    \"Headings, lists, alerts, and more\",\n}))\n\n// Fieldset: bordered box with legend\nfmt.Println(ty.Fieldset(\"Server Config\", \"Host:  localhost\\nPort:  8080\\nTLS:   enabled\"))\n\n// Figure: content with caption\nfmt.Println(ty.Figure(ty.CodeBlock(\"SELECT * FROM users\"), \"Figure 1: Query example\"))\nfmt.Println(ty.FigureTop(ty.Table([][]string{\n    {\"Name\", \"Role\"},\n    {\"Alice\", \"Admin\"},\n}), \"Table 1: User roles\"))\n```\n\n```text\n╭─ Server Config ──────────────────────╮\n│ Host:  localhost                     │\n│ Port:  8080                          │\n│ TLS:   enabled                       │\n╰──────────────────────────────────────╯\n```\n\n```go\n// BR inserts a line break\nfmt.Println(ty.P(\"Line one\" + ty.BR() + \"Line two\"))\n\n// Section groups blocks with single newlines instead of double\nfmt.Println(ty.Compose(\n    ty.H2(\"Shopping List\"),\n    ty.Section(\n        ty.H4(\"Fruits\"),\n        ty.UL(\"Apples\", \"Bananas\", \"Cherries\"),\n    ),\n    ty.Section(\n        ty.H4(\"Vegetables\"),\n        ty.UL(\"Carrots\", \"Spinach\"),\n    ),\n))\n```\n\n### Inline styles\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-inline.png\" alt=\"inline styles demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\n| Method                        | Renders as                                                                                  |\n| ----------------------------- | ------------------------------------------------------------------------------------------- |\n| `Code(text, lang)`            | Inline code with background highlight; `lang` is optional, used when a CodeFormatter is set |\n| `Bold(text)`                  | Bold                                                                                        |\n| `Italic(text)`                | Italic                                                                                      |\n| `Underline(text)`             | Underlined                                                                                  |\n| `Strikethrough(text)`         | Strikethrough                                                                               |\n| `Small(text)`                 | Faint                                                                                       |\n| `Mark(text)`                  | Highlighted background                                                                      |\n| `Link(label, url)`            | Styled link; `url` is optional - when both differ, renders as `label (url)`                 |\n| `Kbd(text)`                   | Keyboard key indicator                                                                      |\n| `Abbr(abbr, desc)`            | Abbreviation; `desc` is optional, appended in parentheses                                   |\n| `Sub(text)`                   | Renders with `_` prefix (style not configurable via options)                                |\n| `Sup(text)`                   | Renders with `^` prefix (style not configurable via options)                                |\n| `Ins(text)`                   | Inserted text, prefixed with `+`                                                            |\n| `Del(text)`                   | Deleted text, prefixed with `-`, strikethrough                                              |\n| `Q(text)`                     | Inline quotation with styled curly quotes (\\u201C \\u201D)                                   |\n| `Cite(text)`                  | Citation styling (italic + muted)                                                           |\n| `Samp(text)`                  | Sample output styling (monospace, distinct from `Code`)                                     |\n| `Var(text)`                   | Variable name styling (italic + accent color)                                               |\n| `Badge(text)`                 | Styled pill/tag label (e.g. `[SUCCESS]`, `[BETA]`)                                          |\n| `BadgeWithStyle(text, style)` | Badge with a one-off style override                                                         |\n| `Tag(text)`                   | Subtle pill/category label (lighter variant of Badge)                                       |\n| `TagWithStyle(text, style)`   | Tag with a one-off style override                                                           |\n| `SuccessBadge(text)`          | Badge using the theme's success color (green)                                               |\n| `WarningBadge(text)`          | Badge using the theme's warning color (amber)                                               |\n| `ErrorBadge(text)`            | Badge using the theme's error color (red)                                                   |\n| `InfoBadge(text)`             | Badge using the theme's info color (blue)                                                   |\n| `SuccessTag(text)`            | Tag using the theme's success color                                                         |\n| `WarningTag(text)`            | Tag using the theme's warning color                                                         |\n| `ErrorTag(text)`              | Tag using the theme's error color                                                           |\n| `InfoTag(text)`               | Tag using the theme's info color                                                            |\n\n```go\nfmt.Println(ty.Bold(\"important\") + \" and \" + ty.Italic(\"nuanced\"))\nfmt.Println(ty.Kbd(\"Ctrl\") + \" + \" + ty.Kbd(\"C\"))\nfmt.Println(ty.Link(\"Go website\", \"https://go.dev\"))\nfmt.Println(ty.Abbr(\"CSS\", \"Cascading Style Sheets\"))\nfmt.Println(ty.Sub(\"2\") + \"O\" + ty.Sup(\"n\"))\nfmt.Println(ty.Ins(\"added line\"))\nfmt.Println(ty.Del(\"removed line\"))\nfmt.Println(ty.Q(\"To be, or not to be\"))\nfmt.Println(ty.Cite(\"The Go Programming Language\"))\nfmt.Println(\"Output: \" + ty.Samp(\"Hello, World!\"))\nfmt.Println(\"Set \" + ty.Var(\"PORT\") + \" to configure the server\")\nfmt.Println(ty.Badge(\"SUCCESS\") + \" \" + ty.Badge(\"BETA\"))\nfmt.Println(ty.Tag(\"v2.0\") + \" \" + ty.Tag(\"go\"))\n\n// Semantic variants use the theme's status colors automatically\nfmt.Println(ty.SuccessBadge(\"running\"), ty.ErrorBadge(\"failed\"), ty.WarningBadge(\"expiring\"), ty.InfoBadge(\"pending\"))\nfmt.Println(ty.SuccessTag(\"healthy\"), ty.ErrorTag(\"critical\"), ty.WarningTag(\"degraded\"), ty.InfoTag(\"maintenance\"))\n```\n\n```text\nimportant and nuanced\n[Ctrl] + [C]\nGo website (https://go.dev)\nCSS (Cascading Style Sheets)\n_2O^n\n+added line\n-removed line\nSUCCESS  BETA\nv2.0  go\n```\n\nIn a color terminal, `Badge` renders with a filled background pill and `Tag` with a lighter variant.\n\n### Lists\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-lists.png\" alt=\"lists demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\n| Method             | Description                                              |\n| ------------------ | -------------------------------------------------------- |\n| `UL(items...)`     | Unordered list with bullet character (default `•`)       |\n| `OL(items...)`     | Ordered list with `1.`, `2.`, `3.` markers               |\n| `NestUL(items...)` | Nested unordered list with bullet cycling                |\n| `NestOL(items...)` | Nested ordered list with optional hierarchical numbering |\n\n```go\nfmt.Println(ty.UL(\"Apples\", \"Bananas\", \"Cherries\"))\nfmt.Println(ty.OL(\"First\", \"Second\", \"Third\"))\n```\n\n#### Nested lists\n\n`NestUL` and `NestOL` render hierarchical lists with configurable indentation, bullet cycling, and mixed nesting.\n\n**Constructors:**\n\n| Function                                | Description                         |\n| --------------------------------------- | ----------------------------------- |\n| `Item(text)`                            | Leaf item (no children)             |\n| `Items(texts...)`                       | Batch-convert strings to leaf items |\n| `ItemWithChildren(text, children...)`   | Item with unordered sub-list        |\n| `ItemWithOLChildren(text, children...)` | Item with ordered sub-list          |\n\n```go\n// Nested unordered list with mixed sub-lists\nfmt.Println(ty.NestUL(\n    herald.Item(\"Fruits\"),\n    herald.ItemWithChildren(\"Vegetables\",\n        herald.Item(\"Carrots\"),\n        herald.Item(\"Peas\"),\n    ),\n    herald.ItemWithOLChildren(\"Ranked Desserts\",\n        herald.Item(\"Ice cream\"),\n        herald.Item(\"Cake\"),\n    ),\n))\n```\n\n```text\n• Fruits\n• Vegetables\n  ◦ Carrots\n  ◦ Peas\n• Ranked Desserts\n  1. Ice cream\n  2. Cake\n```\n\n```go\n// Nested ordered list\nfmt.Println(ty.NestOL(\n    herald.Item(\"Introduction\"),\n    herald.ItemWithOLChildren(\"Main Topics\",\n        herald.Item(\"Architecture\"),\n        herald.Item(\"Design\"),\n    ),\n    herald.Item(\"Conclusion\"),\n))\n```\n\n```text\n1. Introduction\n2. Main Topics\n  1. Architecture\n  2. Design\n3. Conclusion\n```\n\nEnable `WithHierarchicalNumbers(true)` for outline-style numbering (`2.1.`, `2.2.`):\n\n```go\nty := herald.New(herald.WithHierarchicalNumbers(true))\n\nfmt.Println(ty.NestOL(\n    herald.Item(\"Introduction\"),\n    herald.ItemWithOLChildren(\"Main Topics\",\n        herald.Item(\"Architecture\"),\n        herald.Item(\"Design\"),\n    ),\n    herald.Item(\"Conclusion\"),\n))\n```\n\n```text\n1. Introduction\n2. Main Topics\n  2.1. Architecture\n  2.2. Design\n3. Conclusion\n```\n\n### Tables\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-tables.png\" alt=\"tables demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\n`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).\n\n```go\nfmt.Println(ty.Table([][]string{\n    {\"Name\", \"Role\", \"Status\"},\n    {\"Alice\", \"Admin\", \"Active\"},\n    {\"Bob\", \"Editor\", \"Idle\"},\n}))\n```\n\n**Bordered (default):**\n\n```text\n┌───────┬────────┬────────┐\n│ Name  │ Role   │ Status │\n├───────┼────────┼────────┤\n│ Alice │ Admin  │ Active │\n│ Bob   │ Editor │ Idle   │\n└───────┴────────┴────────┘\n```\n\n**Minimal:**\n\n```go\nty := herald.New(herald.WithTableBorderSet(herald.MinimalBorderSet()))\n```\n\n```text\n Name  │ Role   │ Status\n───────┼────────┼────────\n Alice │ Admin  │ Active\n Bob   │ Editor │ Idle\n```\n\n`TableWithOpts(rows [][]string, opts ...TableOption)` accepts per-table options for column alignment, row separators, striped rows, captions, and footer rows:\n\n```go\n// Column alignment, footer row, and caption\nfmt.Println(ty.TableWithOpts([][]string{\n    {\"Item\", \"Qty\", \"Price\"},\n    {\"Widget\", \"10\", \"$9.99\"},\n    {\"Gadget\", \"5\", \"$24.50\"},\n    {\"Total\", \"15\", \"$34.49\"},\n},\n    herald.WithCaption(\"Order Summary\"),\n    herald.WithFooterRow(true),\n    herald.WithColumnAlign(1, herald.AlignRight),\n    herald.WithColumnAlign(2, herald.AlignRight),\n    // Or set all column alignments at once\n    // herald.WithColumnAligns(herald.AlignLeft, herald.AlignRight, herald.AlignRight),\n))\n```\n\n```go\n// Truncate long cell content\nty.TableWithOpts(rows,\n    herald.WithMaxColumnWidth(15),\n)\n```\n\n| Table option                  | Description                                           |\n| ----------------------------- | ----------------------------------------------------- |\n| `WithColumnAlign(col, align)` | Set alignment for a column (`AlignLeft/Center/Right`) |\n| `WithColumnAligns(aligns...)` | Set alignments for all columns positionally           |\n| `WithRowSeparators(true)`     | Horizontal lines between body rows                    |\n| `WithStripedRows(true)`       | Alternating row background for readability            |\n| `WithCaption(text)`           | Caption above the table                               |\n| `WithCaptionBottom(text)`     | Caption below the table                               |\n| `WithFooterRow(true)`         | Treat last row as a styled footer with separator      |\n| `WithMaxColumnWidth(n)`       | Truncate all columns to `n` chars with `…`            |\n| `WithColumnMaxWidth(col, n)`  | Truncate a specific column (overrides global max)     |\n\n### Alerts\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePreview\u003c/b\u003e\u003c/summary\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-alerts.png\" alt=\"alerts demo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\u003c/details\u003e\n\nGitHub-style alert callouts with colored bars, icons, and labels. Five types are supported: Note, Tip, Important, Warning, and Caution.\n\n| Method            | Icon | Color  | Description                       |\n| ----------------- | ---- | ------ | --------------------------------- |\n| `Note(text)`      | `○`  | Blue   | Useful information for the reader |\n| `Tip(text)`       | `▸`  | Green  | Helpful advice                    |\n| `Important(text)` | `‼`  | Purple | Key information                   |\n| `Warning(text)`   | `⚠`  | Amber  | Urgent attention needed           |\n| `Caution(text)`   | `◇`  | Red    | Risk or negative outcomes         |\n\n```go\nfmt.Println(ty.Note(\"Useful information that users should know.\"))\nfmt.Println(ty.Tip(\"Helpful advice for doing things better.\"))\nfmt.Println(ty.Important(\"Key information users need to know.\"))\nfmt.Println(ty.Warning(\"Urgent info that needs immediate attention.\"))\nfmt.Println(ty.Caution(\"Advises about risks or negative outcomes.\"))\n```\n\n```text\n│ ○ Note\n│ Useful information that users should know.\n\n│ ⚠ Warning\n│ Urgent info that needs immediate attention.\n```\n\nSee [`examples/002_alerts/`](examples/002_alerts/) for the full output of all five alert types.\n\nYou can also use the generic `Alert` method with an `AlertType`:\n\n```go\nfmt.Println(ty.Alert(herald.AlertNote, \"Generic alert call.\"))\n```\n\n## Composition patterns\n\nherald provides typography primitives - you compose them for higher-level patterns. Here are some common recipes.\n\n### Status messages\n\nCombine inline styles for colored status output:\n\n```go\nty := herald.New()\n\n// Success / error with Ins/Del\nfmt.Println(ty.Ins(\"Build completed successfully\"))  // green, prefixed with +\nfmt.Println(ty.Del(\"3 tests failed\"))                // red, prefixed with -\n\n// Semantic badges use the theme's status colors automatically\nfmt.Println(ty.SuccessBadge(\"PASS\") + \" \" + \"All checks passed\")\nfmt.Println(ty.ErrorBadge(\"FAIL\") + \" \" + \"Linter found 2 issues\")\nfmt.Println(ty.WarningBadge(\"EXPIRING\") + \" \" + \"Certificate expires in 7 days\")\nfmt.Println(ty.InfoBadge(\"PENDING\") + \" \" + \"Deployment queued\")\n\n// Semantic tags for subtle status labels\nfmt.Println(ty.SuccessTag(\"healthy\") + \" \" + ty.Tag(\"v2.1.0\"))\n\n// Generic Badge/BadgeWithStyle for non-semantic cases\nfmt.Println(ty.Badge(\"BETA\") + \" \" + ty.Tag(\"go\"))\n```\n\n### Annotated sections\n\nPair headings with alerts for contextual guidance:\n\n```go\nfmt.Println(ty.Compose(\n    ty.H2(\"Database Migration\"),\n    ty.Warning(\"Back up your database before proceeding.\"),\n    ty.P(\"Run the following command to apply migrations:\"),\n    ty.CodeBlock(\"go run ./cmd/migrate up\"),\n))\n```\n\n### Author blocks in release notes\n\nUse `AddressCard` for styled contact information:\n\n```go\nfmt.Println(ty.Compose(\n    ty.H2(\"Release v2.0\"),\n    ty.P(\"Major performance improvements and new API surface.\"),\n    ty.AddressCard(\"Maintained by\\nJane Doe\\njane@example.com\"),\n))\n```\n\n### Rich paragraphs with references\n\nCompose inline elements and footnotes within paragraphs:\n\n```go\nfmt.Println(ty.Compose(\n    ty.P(\n        \"herald\" + ty.FootnoteRef(1) + \" is built on \" +\n        ty.Link(\"lipgloss\", \"https://github.com/charmbracelet/lipgloss\") +\n        \" and supports \" + ty.Bold(\"rich text\") + \", \" +\n        ty.Code(\"inline code\") + \", and \" + ty.Kbd(\"Ctrl\") + \"+\" + ty.Kbd(\"C\") +\n        \" key indicators.\",\n    ),\n    ty.FootnoteSection([]string{\"A Go library for TUI typography\"}),\n))\n```\n\n### Tight heading-body groups\n\n`Compose` inserts a blank line (`\\n\\n`) between every block. When a heading (e.g. H4) already has `MarginBottom`, this produces unwanted triple spacing. `Section` solves this by joining its blocks with a single newline, and the resulting group becomes one block to `Compose`:\n\n```go\nfmt.Println(ty.Compose(\n    ty.H2(\"Release Notes\"),\n    ty.Section(\n        ty.H4(\"Bug Fixes\"),\n        ty.UL(\"Fixed crash on empty input\", \"Resolved race condition\"),\n    ),\n    ty.Section(\n        ty.H4(\"Features\"),\n        ty.UL(\"Added Section method\", \"Added BR method\"),\n    ),\n))\n```\n\n### Global padding and framing\n\nherald 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.\n\n**Per-element wrapping** - apply a frame style to each rendered line:\n\n```go\nty := herald.New()\nframe := lipgloss.NewStyle().Padding(0, 2)\n\nfmt.Println(frame.Render(ty.H1(\"Title\")))\nfmt.Println(frame.Render(ty.P(\"Body text with \" + ty.Bold(\"bold\"))))\n```\n\n**Whole-output wrapping** - build all output first, then wrap once:\n\n```go\npage := ty.Compose(\n    ty.H1(\"Title\"),\n    ty.P(\"Body text\"),\n    ty.HR(),\n)\nfmt.Println(frame.Render(page))\n```\n\n## Customization\n\n### Functional options\n\nPass options to `herald.New()` to override individual styles or tokens.\n\n```go\nty := herald.New(\n    herald.WithHRWidth(60),\n    herald.WithBulletChar(\"-\"),\n    herald.WithH1Style(\n        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(\"#FF0000\")),\n    ),\n)\n```\n\n**Style options** - each accepts a `lipgloss.Style`:\n\n#### Headings\n\n| Option                        | Targets            |\n| ----------------------------- | ------------------ |\n| `WithH1Style` - `WithH6Style` | Heading levels 1-6 |\n\n#### Blocks\n\n| Option                   | Targets           |\n| ------------------------ | ----------------- |\n| `WithParagraphStyle`     | `P`               |\n| `WithBlockquoteStyle`    | `Blockquote` text |\n| `WithBlockquoteBarStyle` | `Blockquote` bar  |\n| `WithCodeInlineStyle`    | `Code`            |\n| `WithCodeBlockStyle`     | `CodeBlock`       |\n| `WithHRStyle`            | `HR`              |\n| `WithHRLabelStyle`       | `HRWithLabel`     |\n\n#### Inline\n\n| Option                   | Targets         |\n| ------------------------ | --------------- |\n| `WithBoldStyle`          | `Bold`          |\n| `WithItalicStyle`        | `Italic`        |\n| `WithUnderlineStyle`     | `Underline`     |\n| `WithStrikethroughStyle` | `Strikethrough` |\n| `WithSmallStyle`         | `Small`         |\n| `WithMarkStyle`          | `Mark`          |\n| `WithLinkStyle`          | `Link`          |\n| `WithKbdStyle`           | `Kbd`           |\n| `WithAbbrStyle`          | `Abbr`          |\n| `WithInsStyle`           | `Ins`           |\n| `WithDelStyle`           | `Del`           |\n| `WithQStyle`             | `Q`             |\n| `WithCiteStyle`          | `Cite`          |\n| `WithSampStyle`          | `Samp`          |\n| `WithVarStyle`           | `Var`           |\n\n#### Lists \u0026 definitions\n\n| Option                | Targets                |\n| --------------------- | ---------------------- |\n| `WithListBulletStyle` | Bullet/number marker   |\n| `WithListItemStyle`   | List item text         |\n| `WithDTStyle`         | Definition term        |\n| `WithDDStyle`         | Definition description |\n\n#### Key-value\n\n| Option             | Targets                     |\n| ------------------ | --------------------------- |\n| `WithKVKeyStyle`   | `KV` / `KVGroup` key text   |\n| `WithKVValueStyle` | `KV` / `KVGroup` value text |\n\n#### Address\n\n| Option                       | Targets               |\n| ---------------------------- | --------------------- |\n| `WithAddressStyle`           | `Address`             |\n| `WithAddressCardStyle`       | `AddressCard` content |\n| `WithAddressCardBorderStyle` | `AddressCard` border  |\n\n#### Fieldset\n\n| Option                    | Targets            |\n| ------------------------- | ------------------ |\n| `WithFieldsetStyle`       | `Fieldset` content |\n| `WithFieldsetBorderStyle` | `Fieldset` border  |\n| `WithFieldsetLegendStyle` | `Fieldset` legend  |\n\n#### Figure\n\n| Option                   | Targets          |\n| ------------------------ | ---------------- |\n| `WithFigureCaptionStyle` | `Figure` caption |\n\n#### Badge \u0026 Tag\n\n| Option                    | Targets                |\n| ------------------------- | ---------------------- |\n| `WithBadgeStyle`          | `Badge`                |\n| `WithTagStyle`            | `Tag`                  |\n| `WithSemanticPalette(sp)` | All 8 semantic methods |\n| `WithSuccessBadgeStyle`   | `SuccessBadge`         |\n| `WithWarningBadgeStyle`   | `WarningBadge`         |\n| `WithErrorBadgeStyle`     | `ErrorBadge`           |\n| `WithInfoBadgeStyle`      | `InfoBadge`            |\n| `WithSuccessTagStyle`     | `SuccessTag`           |\n| `WithWarningTagStyle`     | `WarningTag`           |\n| `WithErrorTagStyle`       | `ErrorTag`             |\n| `WithInfoTagStyle`        | `InfoTag`              |\n\n#### Footnotes\n\n| Option                     | Targets                   |\n| -------------------------- | ------------------------- |\n| `WithFootnoteRefStyle`     | `FootnoteRef`             |\n| `WithFootnoteItemStyle`    | `FootnoteSection` items   |\n| `WithFootnoteDividerStyle` | `FootnoteSection` divider |\n\n#### Tables\n\n| Option                      | Targets                 |\n| --------------------------- | ----------------------- |\n| `WithTableHeaderStyle`      | Table header cells      |\n| `WithTableCellStyle`        | Table body cells        |\n| `WithTableStripedCellStyle` | Alternating body rows   |\n| `WithTableFooterStyle`      | Table footer row        |\n| `WithTableCaptionStyle`     | Table caption text      |\n| `WithTableBorderStyle`      | Table border characters |\n\n#### Alerts\n\n| Option                        | Targets             |\n| ----------------------------- | ------------------- |\n| `WithAlertStyle(type, style)` | Alert of given type |\n\n#### Callbacks\n\n| Option                  | Targets                                                 |\n| ----------------------- | ------------------------------------------------------- |\n| `WithCodeFormatter(fn)` | Syntax-highlighting callback for `Code` and `CodeBlock` |\n\n---\n\n**Token options** - each accepts a `string`, `int`, or `bool`:\n\n#### Heading tokens\n\n| Option                   | Default | Description                    |\n| ------------------------ | ------- | ------------------------------ |\n| `WithH1UnderlineChar(c)` | `═`     | Underline character for H1     |\n| `WithH2UnderlineChar(c)` | `─`     | Underline character for H2     |\n| `WithH3UnderlineChar(c)` | `·`     | Underline character for H3     |\n| `WithHeadingBarChar(c)`  | `▎`     | Bar prefix character for H4-H6 |\n\n#### List tokens\n\n| Option                         | Default            | Description                                           |\n| ------------------------------ | ------------------ | ----------------------------------------------------- |\n| `WithBulletChar(c)`            | `•`                | Bullet character for `UL`                             |\n| `WithNestedBulletChars(chars)` | `•`, `◦`, `▪`, `▹` | Bullet characters cycling per depth for `NestUL`      |\n| `WithListIndent(n)`            | `2`                | Spaces per nesting level for `NestUL`/`NestOL`        |\n| `WithHierarchicalNumbers(b)`   | `false`            | Outline-style numbering for nested `OL` (e.g. `2.1.`) |\n\n#### Block tokens\n\n| Option                        | Default | Description                             |\n| ----------------------------- | ------- | --------------------------------------- |\n| `WithHRChar(c)`               | `─`     | Character repeated for `HR`             |\n| `WithHRWidth(w)`              | `40`    | Width of `HR` in characters             |\n| `WithBlockquoteBar(c)`        | `│`     | Left bar character for `Blockquote`     |\n| `WithCodeLineNumbers(b)`      | `false` | Show line numbers in `CodeBlock`        |\n| `WithCodeLineNumberSep(c)`    | `│`     | Separator between line numbers and code |\n| `WithCodeLineNumberOffset(n)` | `1`     | Starting line number for code blocks    |\n\n#### Inline tokens\n\n| Option              | Default  | Description                      |\n| ------------------- | -------- | -------------------------------- |\n| `WithInsPrefix(c)`  | `+`      | Prefix for `Ins` (inserted text) |\n| `WithDelPrefix(c)`  | `-`      | Prefix for `Del` (deleted text)  |\n| `WithQuoteOpen(c)`  | `\\u201C` | Opening quote character for `Q`  |\n| `WithQuoteClose(c)` | `\\u201D` | Closing quote character for `Q`  |\n\n#### Fieldset tokens\n\n| Option                 | Default | Description                                 |\n| ---------------------- | ------- | ------------------------------------------- |\n| `WithFieldsetWidth(w)` | `0`     | Default width for `Fieldset` (0 = auto-fit) |\n\n#### Key-value tokens\n\n**Theme-level** (set via `herald.New()`):\n\n| Option               | Default | Description                                       |\n| -------------------- | ------- | ------------------------------------------------- |\n| `WithKVSeparator(c)` | `:`     | Separator between key and value in `KV`/`KVGroup` |\n\n**Per-call** (passed to `KVGroupWithOpts`):\n\n| Option                    | Default       | Description                                                |\n| ------------------------- | ------------- | ---------------------------------------------------------- |\n| `WithKVGroupSeparator(s)` | theme default | Override separator for this call (empty string = no colon) |\n| `WithKVRawKeys(bool)`     | `false`       | Skip applying KVKey style (keys are pre-styled)            |\n| `WithKVRawValues(bool)`   | `false`       | Skip applying KVValue style (values are pre-styled)        |\n| `WithKVIndent(n)`         | `0`           | Prepend n spaces of indentation to each line               |\n\n#### Table tokens\n\n| Option                   | Default          | Description                                                 |\n| ------------------------ | ---------------- | ----------------------------------------------------------- |\n| `WithTableBorderSet(bs)` | `BoxBorderSet()` | Border character set (`BoxBorderSet` or `MinimalBorderSet`) |\n| `WithTableCellPad(n)`    | `1`              | Spaces of padding inside each table cell                    |\n\n#### Footnote tokens\n\n| Option                        | Default | Description                                     |\n| ----------------------------- | ------- | ----------------------------------------------- |\n| `WithFootnoteDividerChar(c)`  | `─`     | Character repeated for footnote section divider |\n| `WithFootnoteDividerWidth(w)` | `20`    | Width of footnote section divider               |\n\n#### Alert tokens\n\n| Option                        | Default  | Description                              |\n| ----------------------------- | -------- | ---------------------------------------- |\n| `WithAlertBar(c)`             | `│`      | Left bar character for alerts            |\n| `WithAlertIcon(type, icon)`   | per-type | Override icon for a specific alert type  |\n| `WithAlertLabel(type, label)` | per-type | Override label for a specific alert type |\n\n### Code formatting\n\n`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.\n\n```go\nimport (\n    \"strings\"\n\n    \"github.com/alecthomas/chroma/v2/quick\"\n    \"github.com/indaco/herald\"\n)\n\nfunc chromaFormatter(style string) func(code, language string) string {\n    return func(code, language string) string {\n        var buf strings.Builder\n        err := quick.Highlight(\u0026buf, code, language, \"terminal256\", style)\n        if err != nil {\n            return code\n        }\n        return strings.TrimRight(buf.String(), \"\\n\")\n    }\n}\n\nty := herald.New(\n    herald.WithCodeFormatter(chromaFormatter(\"catppuccin-mocha\")),\n)\n\nfmt.Println(ty.CodeBlock(`func main() { fmt.Println(\"hello\") }`, \"go\"))\n```\n\nSee [`examples/200_chroma-syntax-highlighting/`](examples/200_chroma-syntax-highlighting/) for a chroma-based example, or [`examples/201_tree-sitter-syntax-highlighting/`](examples/201_tree-sitter-syntax-highlighting/) for a tree-sitter-based alternative.\n\n### Line numbers in code blocks\n\nEnable 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.\n\n```go\nty := herald.New(\n    herald.WithCodeLineNumbers(true),\n)\n\nfmt.Println(ty.CodeBlock(\"func main() {\\n\\tfmt.Println(\\\"hello\\\")\\n}\", \"go\"))\n```\n\n```text\n1│ func main() {\n2│     fmt.Println(\"hello\")\n3│ }\n```\n\nWhen displaying a snippet from a larger file, set a custom starting line number with `WithCodeLineNumberOffset`:\n\n```go\nty := herald.New(\n    herald.WithCodeLineNumbers(true),\n    herald.WithCodeLineNumberOffset(42),\n)\n\nfmt.Println(ty.CodeBlock(\"func greet(name string) string {\\n\\treturn \\\"Hello, \\\" + name\\n}\"))\n```\n\n```text\n42│ func greet(name string) string {\n43│     return \"Hello, \" + name\n44│ }\n```\n\nCustomize the separator and style:\n\n```go\nty := herald.New(\n    herald.WithCodeLineNumbers(true),\n    herald.WithCodeLineNumberSep(\":\"),\n    herald.WithCodeLineNumberStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(\"#888888\"))),\n)\n```\n\nThe following option controls the visual appearance of line numbers:\n\n| Option                    | Targets                 |\n| ------------------------- | ----------------------- |\n| `WithCodeLineNumberStyle` | Code block line numbers |\n\n## Themes\n\n### Built-in themes\n\nherald ships with named themes that match [huh](https://charm.land/huh)'s built-in color palettes. Colors auto-adapt to light/dark terminal backgrounds using `lipgloss.HasDarkBackground`. See [Pairing with huh](#pairing-with-huh) for how to use matching themes across herald and huh.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" valign=\"middle\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-theme-dracula.png\" alt=\"Dracula theme demo\" width=\"280\" /\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eDraculaTheme()\u003c/code\u003e\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" valign=\"middle\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-theme-catppuccin.png\" alt=\"Catppuccin theme demo\" width=\"280\" /\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eCatppuccinTheme()\u003c/code\u003e\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" valign=\"middle\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-theme-base16.png\" alt=\"Base16 theme demo\" width=\"280\" /\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eBase16Theme()\u003c/code\u003e\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" valign=\"middle\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/herald/demo-theme-charm.png\" alt=\"Charm theme demo\" width=\"280\" /\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eCharmTheme()\u003c/code\u003e\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n```go\nty := herald.New(herald.WithTheme(herald.DraculaTheme()))\n```\n\n### Color palette\n\n`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`.\n\n| Field       | Maps to                                                                                                                                                                                                                      |\n| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `Primary`   | H1 headings                                                                                                                                                                                                                  |\n| `Secondary` | H2, list bullets, `Badge` background, `Tag` foreground                                                                                                                                                                       |\n| `Tertiary`  | H3, links, `Ins`, `FootnoteRef`                                                                                                                                                                                              |\n| `Accent`    | H4, mark background, `Var`                                                                                                                                                                                                   |\n| `Highlight` | H5, `Abbr`, `Del`                                                                                                                                                                                                            |\n| `Muted`     | H6, blockquote, HR, `HRLabel`, sub/sup, `DD`, `KVKey`, `Address`, `AddressCard`, `AddressCardBorder`, `FootnoteItem`, `FootnoteDivider`, line numbers, table border, caption, `Q`, `Cite`, `FigureCaption`, `FieldsetBorder` |\n| `Text`      | Body text, paragraphs, list items, inline code, `DT`, `KVValue`, table cells, footer, `Samp`, `Fieldset` content                                                                                                             |\n| `Surface`   | Background for `Kbd`, `Tag`, striped table rows                                                                                                                                                                              |\n| `Base`      | Background for inline code, code blocks; mark fg, `Badge` fg                                                                                                                                                                 |\n\nPass the palette to `New()` via `WithPalette`, or call `ThemeFromPalette` to construct a `Theme` value directly.\n\nUse `lipgloss.LightDark` to define adaptive colors that automatically adjust to the terminal's background:\n\n```go\nlightDark := lipgloss.LightDark(lipgloss.HasDarkBackground(os.Stdin, os.Stdout))\n\n// Nord-inspired palette\npalette := herald.ColorPalette{\n    Primary:   lightDark(lipgloss.Color(\"#5E81AC\"), lipgloss.Color(\"#88C0D0\")),\n    Secondary: lightDark(lipgloss.Color(\"#81A1C1\"), lipgloss.Color(\"#81A1C1\")),\n    Tertiary:  lightDark(lipgloss.Color(\"#8FBCBB\"), lipgloss.Color(\"#8FBCBB\")),\n    Accent:    lightDark(lipgloss.Color(\"#EBCB8B\"), lipgloss.Color(\"#EBCB8B\")),\n    Highlight: lightDark(lipgloss.Color(\"#BF616A\"), lipgloss.Color(\"#BF616A\")),\n    Muted:     lightDark(lipgloss.Color(\"#7B88A1\"), lipgloss.Color(\"#4C566A\")),\n    Text:      lightDark(lipgloss.Color(\"#2E3440\"), lipgloss.Color(\"#ECEFF4\")),\n    Surface:   lightDark(lipgloss.Color(\"#D8DEE9\"), lipgloss.Color(\"#3B4252\")),\n    Base:      lightDark(lipgloss.Color(\"#ECEFF4\"), lipgloss.Color(\"#2E3440\")),\n}\n\nty := herald.New(herald.WithPalette(palette))\n```\n\nEach `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()`.\n\nPlain `lipgloss.Color` values (without `LightDark`) work too - they apply the same color regardless of terminal background.\n\n#### Semantic palette\n\n`SemanticPalette` defines four status colors used to derive the themed `SuccessBadge`, `WarningBadge`, `ErrorBadge`, `InfoBadge`, `SuccessTag`, `WarningTag`, `ErrorTag`, and `InfoTag` styles.\n\n| Field     | Semantic meaning              | Default derivation from `ColorPalette` |\n| --------- | ----------------------------- | -------------------------------------- |\n| `Success` | Running, passed, healthy      | `Tertiary` (green in most themes)      |\n| `Warning` | Expiring, degraded            | `Accent` (amber in most themes)        |\n| `Error`   | Failed, critical, down        | `Highlight` (red in most themes)       |\n| `Info`    | Informational, neutral status | `Secondary` (blue in most themes)      |\n\n`ThemeFromPalette` automatically derives a `SemanticPalette` from your `ColorPalette`, so existing custom palettes produce valid semantic badge and tag styles without any changes.\n\nUse `WithSemanticPalette` to override all four semantic colors at once:\n\n```go\nty := herald.New(\n    herald.WithSemanticPalette(herald.SemanticPalette{\n        Success: lipgloss.Color(\"#22c55e\"),\n        Warning: lipgloss.Color(\"#f59e0b\"),\n        Error:   lipgloss.Color(\"#ef4444\"),\n        Info:    lipgloss.Color(\"#3b82f6\"),\n    }),\n)\n```\n\nIndividual styles can be overridden with `WithSuccessBadgeStyle`, `WithErrorTagStyle`, and the other per-variant options listed in [Badge \u0026 Tag](#badge--tag).\n\n#### Alert palette\n\n`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-\u003eNote`, `Success-\u003eTip`, `Warning-\u003eWarning`, `Error-\u003eCaution`), with `Important` using `ColorPalette.Secondary`. Changing the semantic palette therefore updates alert colors too.\n\nUse `WithAlertPalette` to override all 5 alert colors independently:\n\n```go\nty := herald.New(\n    herald.WithAlertPalette(herald.AlertPalette{\n        Note:      lightDark(lipgloss.Color(\"#0969DA\"), lipgloss.Color(\"#58A6FF\")),\n        Tip:       lightDark(lipgloss.Color(\"#1A7F37\"), lipgloss.Color(\"#3FB950\")),\n        Important: lightDark(lipgloss.Color(\"#8250DF\"), lipgloss.Color(\"#D2A8FF\")),\n        Warning:   lightDark(lipgloss.Color(\"#9A6700\"), lipgloss.Color(\"#D29922\")),\n        Caution:   lightDark(lipgloss.Color(\"#CF222E\"), lipgloss.Color(\"#F85149\")),\n    }),\n)\n```\n\nIndividual alert icons and labels can also be customized:\n\n```go\nty := herald.New(\n    herald.WithAlertIcon(herald.AlertTip, \"💡\"),\n    herald.WithAlertLabel(herald.AlertNote, \"Info\"),\n)\n```\n\nYou can combine `WithPalette` with other options to override specific fields after the palette is applied:\n\n```go\nty := herald.New(\n    herald.WithPalette(palette),\n    herald.WithHRWidth(60),\n    herald.WithBulletChar(\"-\"),\n)\n```\n\n### Custom theme\n\nThe easiest way to customize is to start from an existing theme and modify specific fields:\n\n```go\ncustom := herald.DefaultTheme()\ncustom.H1 = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(\"#FF0000\")).MarginBottom(1)\ncustom.BulletChar = \"-\"\n\nty := herald.New(herald.WithTheme(custom))\n```\n\nFor a fully custom theme, construct a `Theme` struct directly:\n\n```go\ncustom := herald.Theme{\n    H1:        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(\"#FFFFFF\")),\n    H2:        lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(\"#AAAAAA\")),\n    Paragraph: lipgloss.NewStyle().MarginBottom(1),\n    // set remaining Theme fields as needed...\n\n    H1UnderlineChar: \"=\",\n    H2UnderlineChar: \"-\",\n    H3UnderlineChar: \".\",\n    HeadingBarChar:  \"\u003e\",\n    BulletChar:      \"*\",\n    HRChar:          \"-\",\n    HRWidth:         40,\n    BlockquoteBar:   \"|\",\n}\n\nty := herald.New(herald.WithTheme(custom))\n```\n\n## Pairing with huh\n\nherald is designed to complement [huh](https://github.com/charmbracelet/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.\n\nSince 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.\n\n```go\nty := herald.New(herald.WithTheme(herald.DraculaTheme()))\n\nfmt.Println(ty.H1(\"Project Setup\"))\nfmt.Println(ty.P(\"Answer a few questions to scaffold your project.\"))\n\nform := huh.NewForm(\n    huh.NewGroup(\n        huh.NewInput().Title(\"Project name\").Value(\u0026name),\n        huh.NewSelect[string]().Title(\"Language\").Options(...).Value(\u0026lang),\n    ),\n).WithTheme(huh.ThemeDracula())\nform.Run()\n\nfmt.Println(ty.H2(\"Summary\"))\nfmt.Println(ty.DL([][2]string{\n    {\"Name\", name},\n    {\"Language\", lang},\n}))\n```\n\nSee [`examples/203_huh-form/`](./examples/203_huh-form) for a runnable example, and [`examples/204_huh-wizard/`](./examples/204_huh-wizard) for a multi-step wizard combining herald and huh.\n\n## Pairing with bubbletea\n\nherald works inside [bubbletea](https://github.com/charmbracelet/bubbletea) applications - build your content with herald, then display it in a bubbletea viewport or model. Herald handles the typography, bubbletea handles the interactivity.\n\n```go\nfunc buildContent(ty *herald.Typography) string {\n    return ty.Compose(\n        ty.H1(\"Release Notes\"),\n        ty.Badge(\"STABLE\")+\" \"+ty.Tag(\"v2.0.0\"),\n        ty.HRWithLabel(\"Features\"),\n        ty.UL(\"New dashboard\", \"Dark mode support\"),\n        ty.Tip(\"Run `go get -u` to upgrade.\"),\n    )\n}\n\n// Pass to a bubbles viewport for scrolling\nm.viewport.SetContent(buildContent(ty))\n```\n\nSee [`examples/205_bubbletea-release-viewer/`](./examples/205_bubbletea-release-viewer) for a scrollable release notes viewer and [`examples/206_bubbletea-explorer/`](./examples/206_bubbletea-explorer) for a sidebar + viewport explorer.\n\n## Pairing with tview\n\nherald works with [tview](https://github.com/rivo/tview) via `tview.ANSIWriter`, which translates lipgloss ANSI output into tview's internal color tags.\n\n```go\nty := herald.New()\n\ntextView := tview.NewTextView().\n    SetDynamicColors(true).\n    SetScrollable(true).\n    SetWordWrap(true)\n\nw := tview.ANSIWriter(textView)\nfmt.Fprintln(w, ty.H1(\"Herald + tview\"))\nfmt.Fprintln(w, ty.P(\"ANSI escape sequences are converted to tview color tags.\"))\nfmt.Fprintln(w, ty.UL(\"Headings\", \"Lists\", \"Alerts\", \"Tables\"))\n```\n\nSee [`examples/207_tview-explorer/`](./examples/207_tview-explorer) for a sidebar + content pane explorer.\n\n## Examples\n\nRunnable examples are in the [`examples/`](examples/) directory:\n\n| Example                                                                                | Description                                                                       |\n| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |\n| [000_default-theme](examples/000_default-theme/)                                       | All elements with the default Rose Pine theme                                     |\n| [001_lists](examples/001_lists/)                                                       | Flat, nested, mixed, and hierarchical lists                                       |\n| [002_alerts](examples/002_alerts/)                                                     | GitHub-style alert callouts (Note, Tip, Important, Warning, Caution)              |\n| [003_table](examples/003_table/)                                                       | Table rendering: bordered, minimal, alignment, striped rows, captions, and footer |\n| [004_semantic-badges](examples/004_semantic-badges/)                                   | Semantic badge and tag methods with default and custom `SemanticPalette`          |\n| [005_compose](examples/005_compose/)                                                   | Compose multiple rendered blocks into a single output                             |\n| [006_section](examples/006_section/)                                                   | Section groups heading + content tightly; BR for line breaks                      |\n| [100_custom-options](examples/100_custom-options/)                                     | Override styles, decoration chars, and tokens via functional options              |\n| [101_custom-palette](examples/101_custom-palette/)                                     | Custom adaptive theme from 9 colors using `ColorPalette` and `LightDark`          |\n| [102_builtin-themes](examples/102_builtin-themes/)                                     | Built-in themes (Dracula, Catppuccin, Base16, Charm) matching huh                 |\n| [103_catppuccin-theme](examples/103_catppuccin-theme/)                                 | Build a full theme from the [Catppuccin](https://catppuccin.com) palette          |\n| [200_chroma-syntax-highlighting](examples/200_chroma-syntax-highlighting/)             | Plug in chroma for syntax-highlighted code blocks                                 |\n| [201_tree-sitter-syntax-highlighting](examples/201_tree-sitter-syntax-highlighting/)   | Plug in tree-sitter for AST-based syntax highlighting                             |\n| [202_gotreesitter-syntax-highlighting](examples/202_gotreesitter-syntax-highlighting/) | Pure-Go tree-sitter highlighting via gotreesitter                                 |\n| [203_huh-form](examples/203_huh-form/)                                                 | Using herald with huh for interactive TUI forms                                   |\n| [204_huh-wizard](examples/204_huh-wizard/)                                             | Multi-step project scaffolder with herald + huh                                   |\n| [205_bubbletea-release-viewer](examples/205_bubbletea-release-viewer/)                 | Scrollable release notes viewer with bubbletea viewport                           |\n| [206_bubbletea-explorer](examples/206_bubbletea-explorer/)                             | Sidebar + scrollable content pane explorer with bubbletea                         |\n| [207_tview-explorer](examples/207_tview-explorer/)                                     | Sidebar + scrollable content pane explorer with tview                             |\n| [208_figure-with-image](examples/208_figure-with-image/)                               | `Figure` with ASCII art image rendering via image2ascii                           |\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findaco%2Fherald","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Findaco%2Fherald","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findaco%2Fherald/lists"}