# Herb: a parser that reads HTML and ERB as one document

> Herb is a parser and toolchain for HTML+ERB templates that reads the markup and the Ruby in a.html.erb file as a single document, which is what lets a linter, a formatter, a language server and a rendering engine all share one understanding of the template. It suits a Rails team that wants real diagnostics on templates.

**marcoroth/herb** — 🌿 A modern templating language for the HTML+ERB you already have.

- Repository: https://github.com/marcoroth/herb
- Website: https://herb-tools.dev
- Stars: 1,333 · Forks: 150
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/marcoroth-herb

## The problem is that ERB and HTML are parsed separately, and each is fine alone

Herb is described as a modern templating language for the HTML and ERB you already have, and one language and toolchain that understands your HTML and Ruby together. The concrete problem it solves is in the README's own example: your .html.erb files are already HTML+ERB, and Herb reads the HTML and the Ruby in them as one document, so it can tell you about an unclosed tag, a mismatched element or a missing percent-greater-than sign with the file, the line and what to change, before the page renders. That is a better error message than a runtime failure, and the reason it is possible is that the parser has a syntax tree for the whole file rather than for each half of it separately.

## One parser underwrites the linter, the formatter and the language server

The ecosystem section lists what sits on top of the parser. The Herb Linter finds mistakes and enforces conventions in HTML+ERB templates. The Herb Formatter formats templates consistently and is marked experimental. The Herb Language Server provides diagnostics, hovers and formatting in VS Code, Zed, Neovim and other editors, with the extension published to the Visual Studio Marketplace and to Open VSX. The Herb Dev Server watches templates and patches the open page as you save, also experimental. The Herb Dev Tools give in-browser tools for inspecting and debugging rendered templates. The consistency here is the point: because they all consume the same syntax tree, a lint rule and a formatter rule and a language server hover cannot disagree about what a file means.

## Herb::Engine renders like Erubi and escapes by position

On the rendering side, Herb::Engine is an HTML-aware ERB rendering engine that is API-compatible with Erubi, and the README explains that it renders your templates the way Erubi does and escapes each value for where it sits. That last clause is the interesting one, since HTML-aware escaping is not one rule but several: a value inside an attribute, a value inside a script block and a value in text content are not escaped the same way, and a parser that knows where in the document it is can pick the right one. The Herb Client Runtime updates the parts of a rendered page that changed, in place, and is marked experimental. For Rails the integration is already done: with the Rails 8.2 framework defaults, Rails renders HTML templates with Herb::Engine, and ReActionView makes those templates reactive and brings the error overlays, the dev tools and per-tag instrumentation.

## Three install commands, and the first one adds nothing to your project

The installation section is deliberately layered, from the least invasive to the most integrated. You can check your templates without adding anything to your project.

```sh
gem exec herb analyze
```

Then you add the gem to the project.

```sh
bundle add herb
```

And the JavaScript tools are run through npx.

```sh
npx @herb-tools/linter
npx @herb-tools/formatter
```

The documentation site covers editors, CI and installing from a Git branch. The first command matters for evaluation, because a team can run the analyzer over an existing templates directory and get a picture of the damage before committing to anything, which is a better starting point than adding a gem and turning on a linter in the same afternoon.

## The parser is C, and it is the piece other tools would build on

Under Building on Herb, the README describes the Herb Parser as a fast, portable, HTML-aware ERB parser written in C, with bindings for Ruby, JavaScript, Java, Rust and WebAssembly, and says the projects page lists it together with the libraries for building your own tools on the Herb syntax tree. So the C parser is the shared substrate and the language bindings are the distribution mechanism, not a rewrite in each language. The repository layout bears that out with C source under src/, and separate rust/, java/, javascript/ and wasm/ directories, an ext/ directory for the Ruby extension, a prism/ directory, a playground/ and examples/ directory, and a bench/ directory. The root also carries a Makefile that builds the library and the Ruby extension together, with strict warning flags including -Werror and -pedantic, a debug build at -O0 and a production build at -O3 with -march=native and link time optimisation.

## The monorepo runs on Nx, and the language server is built separately

The root package.json is named @herb-tools/source, is private and carries the MIT licence, and it shows a JavaScript and TypeScript monorepo orchestrated by Nx at version 23.2.1. Every script runs the same shape of command, nx run-many with a target across all packages in parallel, and each one excludes herb-language-server. The dev, build, test and typecheck scripts all follow that pattern, with affected variants that use nx affected so a change only rebuilds what depends on it. That exclusion is the interesting detail, since it means the language server has its own build path, which makes sense for a component that has to run as a process in an editor rather than as a library in a test suite. The other dev dependencies are Playwright with the Vitest browser mode, Vitest with its UI, Vite, rolldown, esbuild with a copy plugin, Prettier, oxlint, PostCSS and concurrently.

## A Ruby gem, a crates.io crate and an npm package, all versioned together

The README carries four release badges at the top, for the Ruby gem on rubygems.org, the crate on crates.io, the npm package @herb-tools/core, and the VS Code Marketplace extension, plus a badge for the Open VSX extension and one for pkg.pr.new. Four published artefacts from one repository is unusual for a template tool and it reflects the shape of the project, which is a C core with bindings and a set of JavaScript tools rather than a single-language gem. The release history shows v0.11.0 on 2026-09-24, v0.10.4 on 2026-09-10 and v0.10.3 on 2026-08-01, so the project is still on a zero major version, and the last push to the repository was on 2026-10-14 with the repository not archived.

## Conclusion

Herb fits a Rails team that has accumulated template errors and wants diagnostics with file, line and the specific fix rather than a stack trace from the renderer, since understanding the HTML and the Ruby in one pass is what makes that possible. It is a poor fit for a team that treats its .html.erb files as opaque strings and edits them in a text editor, and a poor fit if the formatter is your main interest, since the README marks the formatter and the dev server as experimental. Verify first that the Ruby and ERB dialects your templates use are covered by the parser, and note that the formatter and the dev server are flagged experimental, so start with the linter and the language server rather than making formatting a CI gate.

## FAQ

### What does Herb do that a plain ERB linter does not?

The README states that Herb reads the HTML and the Ruby in a .html.erb file as one document, so it can report an unclosed tag, a mismatched element or a missing terminator with the file, the line and what to change, before the page renders. A linter that only sees the Ruby side cannot check the markup.

### How do I try Herb without changing my project?

Run gem exec herb analyze, which the README presents as checking your templates without adding anything to your project. To add the gem itself, the command is bundle add herb, and the JavaScript linter and formatter are run through npx.

### Is Herb compatible with Erubi?

Yes. Herb::Engine is described as an HTML-aware ERB rendering engine that is API-compatible with Erubi, and it escapes each value according to where it sits in the document. With the Rails 8.2 framework defaults, Rails renders HTML templates with Herb::Engine.

### Which parts of Herb are experimental?

The README marks the Herb Formatter, the Herb Dev Server and the Herb Client Runtime as experimental. The linter, the language server, the dev tools and the engine are not marked that way.

## Sources

- [License: MIT](https://github.com/marcoroth/herb/blob/main/LICENSE)
- [marcoroth/herb on GitHub](https://github.com/marcoroth/herb)
- [Project website](https://herb-tools.dev)
- [README](https://github.com/marcoroth/herb/blob/main/README.md)
- [Releases](https://github.com/marcoroth/herb/releases)

---

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