# alecthomas/chroma: a pure Go syntax highlighter for HTML and ANSI

> Chroma turns source code into highlighted HTML or ANSI text from a Go library or a CLI. It is a port of Pygments' lexer and style model, and the v3 alpha changes the iterator API.

**alecthomas/chroma** — A general purpose syntax highlighter in pure Go 

- Repository: https://github.com/alecthomas/chroma
- Stars: 5,048 · Forks: 528
- Language: Go
- License: NOASSERTION
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/alecthomas-chroma

## What Chroma actually replaces in a Go pipeline

If you render code on a server written in Go, the usual route to syntax highlighting is a subprocess: call Pygments, pipe text in, read HTML out. Chroma removes that subprocess. It is a pure Go library, so the highlighting runs inside your binary, and the README describes it as taking source code and other structured text and converting it into syntax highlighted HTML, ANSI-coloured text, and similar formats. The audience is narrow but real: Go services that embed documentation, code review tools, static site generators, and terminal programs that want colour without a C dependency or a Python runtime on the host.

The design is deliberately borrowed. Chroma is based heavily on Pygments and ships translators for Pygments lexers and styles, which is why the language table in the README reads like Pygments' own list, from ABAP and ABNF down to Zig and Z80 Assembly. That inheritance matters for evaluation: you are not judging a new grammar engine, you are judging how faithfully a Go reimplementation covers an existing one, plus what it adds around it (a CLI, a registry, a playground).

The README points readers at a Chroma Playground for trying languages and styles without writing code, which is the fastest way to see whether a given lexer produces output you would ship.

## Lexers, styles and formatters as three separate registries

The architecture is a three-stage pipeline. Lexers convert source text into a stream of tokens. Styles map token types to colours. Formatters turn tokens plus a style into formatted output. Each of those lives in its own package (lexers, formatters, styles) and each package exposes a global Registry variable holding every registered implementation, plus helpers such as looking up a lexer by name or matching a filename.

The failure mode is built into the API: if a lexer, formatter or style cannot be determined, nil is returned. The README's answer is the Fallback value in each package, which provides defaults. This is a clean contract, but it pushes a decision onto you. A nil lexer silently falling back to plaintext means a misconfigured pipeline produces unhighlighted output rather than an error, which is easy to miss in a build log and obvious only when someone looks at the rendered page.

Language identification has three entry points, and the choice affects accuracy. lexers.Match("foo.go") detects from filename, lexers.Get("go") takes an explicit Chroma syntax ID from lexers.Names(), and a third path detects from file content. Filename matching is cheap and usually right; explicit IDs are the only fully predictable option when your users upload files with wrong or missing extensions.

v3 changes the token plumbing rather than the concept. According to the README note, v3 replaces the custom Iterator type with Go's built-in iter.Seq[Token], removes the EOF sentinel, and bumps the module path to github.com/alecthomas/chroma/v3.

## Installing Chroma and highlighting a file from the CLI

The module path is the first thing to get right, because the README states that v2 and v3 use different import paths. The go.mod in the repository declares module github.com/alecthomas/chroma/v3 and requires Go 1.25, so a v3 build needs a toolchain at least that new. The README also notes that v3 is currently an alpha, with releases such as v3.0.0-alpha.5, so pin whatever you pull in.

The repository's Justfile builds the chromad binary with just chromad, and the Dockerfile sets PATH="/app/bin:${PATH}" before running that step, which is how the project builds its own server. For the library itself, the README's import line is the starting point:

```go
import "github.com/alecthomas/chroma/v3"
```

The README says the authoritative list of supported languages can be displayed with chroma --list, and treats the table in the README as something the maintainer tries to keep current rather than a guarantee.

```bash
chroma --list
```

If you are embedding Chroma, the README's quick start is a single call that writes straight to a writer:

```go
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
```

The arguments are the writer, the source text, the language, the formatter and the style, in that order. The README does not document a rollback or uninstall procedure, so treat the module import as the only supported path it describes.

## Where Chroma is the wrong tool

The README keeps a section titled What's missing compared to Pygments, which is the honest signal here: Chroma is a reimplementation, not a superset, and the gap is documented rather than hidden. If your product supports a long tail of languages and you need parity with a Pygments install, check that section and the lexer list before you commit. The README's own wording is that the maintainer will attempt to keep the supported-languages table up to date, which is a statement about intent, not a contract.

The second constraint is version churn. The v3 alpha changes the token iteration type and removes the EOF sentinel, which means code written against v2 does not compile unchanged against v3. For a library embedded in a build pipeline, that is a migration you schedule, not a patch you apply casually. The release history shows several alpha tags within a short window, so the API surface is still moving.

The third is scope. Chroma highlights text. It does not parse, lint, or semantically analyse code, and the README describes no editor integration, language server or incremental re-highlighting. If you need highlighting that updates as a user types in a browser without a round trip, Chroma's server-side model is a different shape of solution, and you would be reimplementing the client half yourself.

## Chroma against Pygments and against client-side highlighters

The obvious alternative is Pygments itself, and the difference is not output quality but deployment. Pygments is Python: you run it as a process or a service, and your Go binary depends on a Python environment being present and correctly versioned. Chroma trades that dependency for a Go module and accepts a smaller language surface. The README's What's missing compared to Pygments section is where that trade is quantified, and it is the first thing to read if your language list is long.

The other family is client-side highlighting, where the browser colours the code. That keeps the server simple and makes highlighting interactive, but it moves work to the user's device and means the highlighted markup is produced by JavaScript rather than by your render pipeline. Chroma's formatters, by contrast, produce HTML or ANSI text server-side, which is what you want if the output is cached, embedded in email, printed to a terminal, or indexed.

A less obvious comparison is with calling Pygments once at build time and shipping static HTML. That works for a documentation site and costs nothing at runtime. Chroma earns its place when highlighting happens per request: user-submitted snippets, code review diffs, log viewers, or a terminal UI that colours output as it is produced.

## Maintenance, licensing and the cost of tracking v3

The repository is not archived, and the last push was on 2026-09-22, so the project is being worked on. The release list is dominated by v3 alphas, the most recent being v3.0.0-alpha.5 on 2026-07-08, which tells you where the maintainer's attention is: the v3 line, not v2. If you adopt v2 today you are adopting the branch that is receiving less change.

The upgrade cost is concentrated in the token API. Moving from v2 to v3 means replacing the custom Iterator with iter.Seq[Token] and dropping code that relies on the EOF sentinel, plus changing the import path to github.com/alecthomas/chroma/v3. Because the module path changed, v2 and v3 can coexist in one dependency graph, which softens the migration but also means a partially migrated codebase can compile with both versions present.

The licence field is reported as NOASSERTION, meaning the repository metadata does not resolve to a standard SPDX identifier. The repository does contain a COPYING file, and the README links to Pygments, whose lexers and styles Chroma translates. If you redistribute Chroma or generated lexer data, read COPYING and the upstream Pygments licence terms yourself; this is not legal advice, and the metadata alone will not tell you what obligations attach.

## Conclusion

Use Chroma when you are already in Go and need highlighted HTML or ANSI text without shelling out to a Python process: the quick.Highlight call and the chroma CLI cover most cases. Do not adopt it if you need Pygments' full lexer set, since the README keeps a section titled What's missing compared to Pygments, or if you cannot tolerate a v3 alpha whose module path is github.com/alecthomas/chroma/v3 and whose Iterator type was replaced by iter.Seq[Token]. Before committing, run chroma --list on your own corpus and check that your target languages and styles appear, then pin the exact version in go.mod.

## FAQ

### How can I use alecthomas/chroma to highlight code?

Either call the library's quick.Highlight with a writer, source text, language, formatter and style, or build the chroma CLI and list its languages. The README's quick start example passes "go", "html" and "monokai" as the language, formatter and style.

### How do I install alecthomas/chroma?

The repository declares the module github.com/alecthomas/chroma/v3, so you add it as a Go module with the README's import path. The README notes that v3 is an alpha and that the import path changed from v2.

### Which languages does alecthomas/chroma support?

The README carries a long table covering languages from ABAP and ABNF to Zig and Z80 Assembly, but states that the authoritative list is displayed with chroma --list. The README also keeps a section on what is missing compared to Pygments.

### What changed in alecthomas/chroma v3?

According to the README note, v3 replaces the custom Iterator type with Go's built-in iter.Seq[Token], removes the EOF sentinel, and bumps the module path to github.com/alecthomas/chroma/v3. The repository's go.mod requires Go 1.25.

### Can alecthomas/chroma output ANSI colours for a terminal?

Yes. The README describes Chroma as converting source code into syntax highlighted HTML, ANSI-coloured text, and similar formats, with formatters as a separate package from lexers and styles.

## Sources

- [alecthomas/chroma on GitHub](https://github.com/alecthomas/chroma)
- [Issues](https://github.com/alecthomas/chroma/issues)
- [README](https://github.com/alecthomas/chroma/blob/master/README.md)
- [Releases](https://github.com/alecthomas/chroma/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/alecthomas-chroma
