Ruby on Rails
By parsing templates into addressable DOM slots at compile time, Rails eliminates frontend JavaScript frameworks by running reactive client state, two-way bindings, and optimistic UI updates directly in standard ERB.
AI-assisted summary of Herb in Rails 8.2: Your ERB views, now HTML-aware - Marco Roth — original source. Check the original for full context.
Source published:
Key ideas
- Rails 8.2 integrates Herb directly into Action View as the default compilation engine for all .html.erb templates, unifying HTML and Ruby into a single semantic AST. (3:00)
- Herb v0.11 delivers an enhanced Language Server with partial reference tracking, runtime SQL instrumentation that maps database queries back to specific ERB tags, and over 160 new view lint rules. (4:24)
- Reaction View 0.6 introduces an addressable 'slots' architecture that compiles templates into lightweight dynamic data payloads, enabling surgical DOM updates over HTTP, WebSockets, or SSE without full DOM morphing. (11:47)
- Client-side state and two-way form bindings run directly in ERB templates without writing JavaScript, bundling Ruby WebAssembly runtimes, or performing Opal code transpilation. (17:48)
- Reaction View incorporates Rust-powered gems lightningcss-ruby and oxc-ruby to support scoped CSS and scoped TypeScript/JSX scripts inside view partials without adding Node.js to the Rails deployment pipeline. (25:38)
Detailed notes
## Background Presented at Rails World 2026 and published on YouTube by the official Ruby on Rails channel on October 1, 2026, this talk features Marco Roth discussing the integration of Herb into Rails 8.2. According to the video description, Herb delivers an HTML-aware toolchain that comprehends Ruby and HTML together, powering linters, language servers, browser dev tools, and native Action View template compilation.
## TL;DR Marco Roth introduces Herb, an HTML-aware templating engine integrated natively into Rails 8.2 that compiles standard ERB templates with full structural and semantic awareness. Alongside developer tooling enhancements in Herb v0.11—such as LSP diagnostics, runtime query hints, and live-reloading—Roth debuts Reaction View 0.6, an architecture that turns ERB expressions into addressable DOM 'slots.' This slot-based model enables client-side UI state, two-way form binding, declarative async loading, and optimistic updates with automatic rollbacks, entirely bypassing the need for client-side JavaScript frameworks. Looking forward, Roth showcases high-performance Rust tooling via Lightning CSS and Oxc to bring scoped component styles and scripts directly into Rails ERB views.
## Atmosphere & style Marco Roth delivers an engaging, technically grounded presentation marked by modest pride and deep appreciation for the open-source community. The pacing builds methodically from low-level compiler mechanics up to impressive live demonstrations, including a responsive, zero-JavaScript music player and an optimistic chat UI. Watching the session feels like witnessing the realization of a long-standing vision for Rails views, replacing years of frontend friction and boilerplate with an elegant, cohesive developer experience.
## Bulletin - [03:00](https://www.youtube.com/watch?v=REtfdqtW3PY&t=180s) Rails 8.2 integrates Herb directly into Action View as the default compilation engine for all .html.erb templates, unifying HTML and Ruby into a single semantic AST. - [04:24](https://www.youtube.com/watch?v=REtfdqtW3PY&t=264s) Herb v0.11 delivers an enhanced Language Server with partial reference tracking, runtime SQL instrumentation that maps database queries back to specific ERB tags, and over 160 new view lint rules. - [11:47](https://www.youtube.com/watch?v=REtfdqtW3PY&t=707s) Reaction View 0.6 introduces an addressable 'slots' architecture that compiles templates into lightweight dynamic data payloads, enabling surgical DOM updates over HTTP, WebSockets, or SSE without full DOM morphing. - [17:48](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1068s) Client-side state and two-way form bindings run directly in ERB templates without writing JavaScript, bundling Ruby WebAssembly runtimes, or performing Opal code transpilation. - [25:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1538s) Reaction View incorporates Rust-powered gems lightningcss-ruby and oxc-ruby to support scoped CSS and scoped TypeScript/JSX scripts inside view partials without adding Node.js to the Rails deployment pipeline.
## Key takeaways - [00:11](https://www.youtube.com/watch?v=REtfdqtW3PY&t=11s) Herb bridges a longstanding tooling gap in the Hotwire ecosystem by parsing HTML and Ruby ERB code into an integrated, structurally aware abstract syntax tree. - [03:00](https://www.youtube.com/watch?v=REtfdqtW3PY&t=180s) In Rails 8.2, Action View routes all .html.erb templates through Herb by default, while non-HTML formats automatically fall back to the legacy ERB template handler. - [04:24](https://www.youtube.com/watch?v=REtfdqtW3PY&t=264s) Herb v0.11 integrates with ActiveSupport::Notifications to project runtime ActiveRecord query diagnostics and N+1 warnings directly as inline hints inside code editors. - [07:17](https://www.youtube.com/watch?v=REtfdqtW3PY&t=437s) Reaction View 0.6 brings fine-grained reactivity to standard Action View templates, providing SPA-like interactivity without requiring frontend JavaScript frameworks. - [11:47](https://www.youtube.com/watch?v=REtfdqtW3PY&t=707s) Herb's slots architecture injects HTML comment delimiters around dynamic ERB tags, allowing individual expressions and partial regions to be addressed and updated surgically in the DOM. - [13:39](https://www.youtube.com/watch?v=REtfdqtW3PY&t=819s) Slot updates are wire-agnostic and rely on just four structural primitives: outputs, slots, collections, and conditionals. - [15:02](https://www.youtube.com/watch?v=REtfdqtW3PY&t=902s) Reaction View uses HTTP content negotiation to return lightweight schema payloads for subsequent dynamic requests while serving standard HTML for initial navigations. - [16:03](https://www.youtube.com/watch?v=REtfdqtW3PY&t=963s) The herb:state directive and data-herb-set attributes manage ephemeral UI state like toggles and accordions locally in the browser, eliminating the need for dedicated Stimulus controllers. - [17:48](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1068s) Reaction View statically compiles a common subset of Ruby view operations into client reactivity models, delivering real-time two-way bindings without Ruby Wasm runtimes. - [19:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1178s) When template expressions invoke database queries or complex domain logic, Herb automatically delegates evaluation to background server requests without requiring custom routes or Turbo Frames. - [21:22](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1282s) The built-in <Async> tag defers slow database queries inline, rendering a fallback skeleton immediately before swapping in resolved server data asynchronously. - [21:57](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1317s) Declarative lazy evaluation allows ERB templates to pause server data fetching until an element scrolls into the user's viewport. - [23:42](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1422s) Reaction View achieves optimistic UI updates and deterministic state rollbacks on network failure by maintaining a client-side transaction log of state transitions. - [25:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1538s) Rust-backed tooling enables scoped CSS and scoped script compilation natively within Rails, paving the way for offline view synchronization and gem-packaged full-stack components.
## Detailed walkthrough ### [00:11](https://www.youtube.com/watch?v=REtfdqtW3PY&t=11s) Introducing Herb: An HTML-Aware View Layer for Rails 8.2
Marco Roth opens his talk at Rails World 2026 by welcoming the audience and expressing his excitement about the official arrival of Herb in Rails 8.2. Over the past few years as a full-stack developer and open-source contributor, Marco has focused extensively on Hotwire, exploring how far developers can push server-side rendered web applications using the HTML-over-the-wire paradigm. He observed a critical gap in the ecosystem: while backend and deployment tooling continuously advanced, the developer experience and development tooling around Hotwire and the Rails view layer lagged behind.
```mermaid flowchart TD accTitle: Herb View Toolchain Architecture accDescr: Shows how Herb's semantic understanding powers tooling and runtime rendering in Rails 8.2.
subgraph Parser["Herb Parser Core"] AST["HTML + Ruby Semantic AST"] end
subgraph DevTooling["Developer Experience Tools"] Linter["Linter (160+ Rules)"] LSP["Language Server (LSP)"] DevServer["Dev Server (Real-Time Hot Reload)"] BrowserTools["Browser DevTools & Runtime Hints"] end
subgraph Runtime["Action View Runtime (Rails 8.2)"] Compiler["Herb Template Engine"] ActionView["Action View Renderer"] end
AST --> DevTooling AST --> Runtime Compiler --> ActionView ```
Herb was created to solve this by providing a unified templating engine and toolchain that treats HTML and Ruby ERB code as a single cohesive structure rather than disconnected strings. Because Herb possesses full structural and semantic understanding of templates, it enables an entire suite of developer utilities: a dedicated linter, a code formatter, a language server protocol (LSP), in-browser debugging tools, a hot-reloading development server, and now an integrated compiler engine that ships directly inside Rails 8.2 Action View.
- **Browser Debug Tools**: Debuted at Rails World the previous year, giving developers visual view outlines in the browser, direct bi-directional jumps to editor code, and deep introspection into rendered partials. - **Herb Dev Server**: Enables real-time browser updates during local view editing without manual page refreshes, automatically updating multiple instances of partials and components across the page simultaneously.
---
[03:00](https://www.youtube.com/watch?v=REtfdqtW3PY&t=180s) Native Action View Integration in Rails 8.2
In Rails 8.2, Herb transitions from an external tool into an internal compilation engine within Action View. Rails templates historically determine rendering through their action name, format, and template handler. In Rails 8.2, Herb becomes the default engine for any template matching the `.html.erb` pattern (the `html` format paired with the `erb` handler). Templates targeting other formats continue to fall back to the legacy ERB engine for safe backwards compatibility.
To help existing codebases transition safely, Rails 8.2 includes a dedicated compatibility check via a Rake task that developers can run across their application to verify whether all templates are ready for Herb compilation. Applications loading Rails 8.2 defaults enable Herb automatically, but teams can configure the template engine back to standard ERB via application configuration if needed.
```mermaid flowchart LR accTitle: Rails 8.2 Action View Template Handler Selection accDescr: Illustrates how Action View routes.html.erb templates to Herb while falling back to legacy ERB for non-HTML formats.
Tpl["Incoming Template"] --> Check{"Format == HTML & Handler == ERB?"} Check -->|"Yes"| HerbEngine["Herb Compilation Engine (Rails 8.2 Default)"] Check -->|"No"| LegacyERB["Fallback ERB Handler"] ```
---
[04:24](https://www.youtube.com/watch?v=REtfdqtW3PY&t=264s) Herb 0.11: LSP Enhancements, Runtime Instrumentation, and Inline Editor Hints
Launching in tandem with Rails 8.2 is Herb v0.11, the largest release of the toolchain to date, bringing substantial enhancements to editor tooling, diagnostics, and developer feedback loops:
- **Language Server (LSP) Features**: Supports multi-app monorepos via first-class workspace support, component/partial previews, automated code actions (such as extracting view selections into reusable partials), and cross-file diagnostics. The LSP introduces reference tracking for partials, letting human developers and autonomous AI agents locate exactly where partials are invoked across a project to refactor views safely. - **Runtime Instrumentation**: Every ERB tag in a view is instrumented and correlated with standard `ActiveSupport::Notifications`. For instance, Herb hooks into `ActiveRecord` SQL query notifications and maps emitted database queries directly back to the exact ERB tag that triggered them. - **Visual Diagnostics & DevTools**: In the browser, developers can inspect runtime diagnostics, view stack traces, and see visual highlights on the rendered page indicating where each query or diagnostic originated. Herb also provides a redesigned error overlay for hot-reloading runtime errors. - **Inline Editor Hints & Hover Popups**: Diagnostics captured at runtime (such as N+1 queries or heavy SQL statements) are transmitted straight back to the editor as inline hints placed directly next to the offending ERB tags, complete with hover tooltips detailing executed SQL queries. - **Translation Overlays**: Localization keys can be rendered in-place within the editor, showing translated string values until the developer places their cursor on the line to reveal the underlying `t(...)` source code. - **Linting Suite**: Ships with over 160 new lint rules designed to keep view code structurally sound and prevent mistakes when developers or AI coding agents edit templates.
Marco thanks the 31 open-source contributors whose pull requests and feedback shaped Herb v0.11 before pointing attendees to the detailed launch post on the official blog.
---
[07:17](https://www.youtube.com/watch?v=REtfdqtW3PY&t=437s) Reaction View: Bringing Fine-Grained Reactivity to Rails Views
Having placed the Herb compiler at the foundation of Rails apps, Marco explores what becomes possible when the runtime truly understands view HTML. Rails established itself as the premier "one-person framework"—Rails 7 simplified frontend architectures via Hotwire's HTML-over-the-wire approach, and Rails 8 streamlined infrastructure through Solid gems (Solid Queue, Solid Cache, Solid Cable) and SQLite. However, Marco argues that the Rails view layer historically failed to scale in expressiveness alongside the rest of the stack.
To build ambitious, modern, and highly interactive user interfaces without abandoning server rendering, Rails needs "fine-grained reactivity". Taking inspiration from past community efforts like StimulusReflex, modern Rails tools (Turbo and Stimulus), and ecosystems like Phoenix LiveView, Laravel Livewire, Inertia.js, and modern JavaScript SPAs, Marco introduces **Reaction View**.
Following a promotional video showcasing instantaneous DOM morphing and state synchronizations, Marco formally announces the release of **Reaction View 0.6**. Reaction View makes standard Action View templates reactive out of the box without requiring frontend JavaScript single-page application frameworks: "everything a client-side framework does, you can now get without having to adopt one."
- **Zero-Migration Adoption**: Any existing `.html.erb` template is already a valid Reaction View template, operating in server mode by default with standard server-driven reactivity. - **Progressive Interactive Levels**: Reaction View uses a graduated adoption scale across six levels. With version 0.6, the team ships Level 4 and Level 5, introducing fully reactive templates and advanced interactivity, while Level 6 (external component architectures) remains in development.
[11:47](https://www.youtube.com/watch?v=REtfdqtW3PY&t=707s) Introducing Slots: Making ERB Expressions Addressable
To render ERB views on the client side, Marco Roth introduces the foundational architectural concept powering Herb: "slots." In this model, the compiler inspects an ERB template, identifies every ERB tag, and treats each dynamic tag as an addressable slot. When a template contains a loop, the engine recognizes the entire structure as a collection into which dynamic items can flow. Because the Herb toolchain possesses full structural and semantic comprehension of the entire template, it understands not just isolated tags, but the broader intent of the markup.
This deep comprehension enables templates to be compiled into two distinct formats. The first is standard HTML sent over the wire for the initial full page load. The second format is compiled specifically for subsequent dynamic requests, where the server transmits only the updated dynamic values rather than full markup.
Under standard Rails rendering, once an ERB expression like `<%= title %>` compiles into HTML, the browser receives a plain string of text. The browser has no knowledge of where that text originated; the connection back to the source ERB tag is completely detached. Herb solves this by automatically injecting HTML comment boundaries into the markup during the compile step. These comment markers cleanly isolate the evaluated expression so it remains individually addressable within the DOM. By wrapping partials and components in boundary tags that designate their source region, Herb knows the exact file and partial from which an element originated, enabling fine-grained, surgical updates to specific DOM nodes without requiring full page reloads or full DOM morphing operations.
```mermaid flowchart TD accTitle: Dual Compilation and Slot Architecture in Herb accDescr: Diagram showing how an ERB template compiles into either full HTML with comment markers or a lightweight slots payload for subsequent updates.
subgraph Input ["Template Analysis"] ERB["ERB Template<br/>(Tags, Loops, Partials)"] Parser["Herb Semantic Parser<br/>(Extracts Slots & Collections)"] ERB --> Parser end
subgraph Outputs ["Dual Output Compilation"] Initial["Initial Render:<br/>Full HTML with HTML Comment Markers"] Subsequent["Subsequent Requests:<br/>Dynamic Slot Values Only"] Parser -->|"First Page Load"| Initial Parser -->|"Reactive Update"| Subsequent end
subgraph Client ["Browser DOM"] DOM["Addressable DOM Slots<br/>(Surgically updated without morphing)"] Initial --> DOM Subsequent -->|"Patches specific slots"| DOM end ```
- Every individual ERB tag within a view template is registered as an addressable slot. - Loops and iterations are structurally compiled as managed collections. - HTML comments serve as persistent DOM delimiters, preserving the template's source boundaries in the browser. - Surgical slot replacement eliminates the overhead and unpredictability of whole-tree DOM morphing.
---
[13:39](https://www.youtube.com/watch?v=REtfdqtW3PY&t=819s) Wire-Agnostic Updates and the Dev Tools Experience
Because both the server and client maintain an identical map of where dynamic values belong and how to update them, slot updates become completely wire-agnostic. Fresh values can arrive over standard HTTP requests, WebSockets via Action Cable, Server-Sent Events (SSE), or even originate purely within the client itself. Regardless of the delivery transport, the client can apply and reflect the changes instantaneously without re-rendering markup on the server. The entire system is built upon just four fundamental structural primitives: outputs, slots, collections, and conditionals.
To demonstrate this in practice, Marco walks through a template rendering the current server time via `<%= Time.now %>`. Adding a button with a `data-herb-action="refresh"` attribute—intentionally designed to mimic the familiar syntax of Stimulus's `data-action`—wires up the interaction. When the user clicks the button, the view triggers a targeted live update in the browser rather than a standard navigation. Herb's companion browser developer tools highlight precisely which slots updated in real time, making the flow of dynamic data completely transparent during development.
- Transport versatility: slot updates can be delivered via HTTP, Action Cable, Server-Sent Events, or client-side evaluation. - The reactivity model relies entirely on four primitives: outputs, slots, collections, and conditionals. - Event binding follows a familiar declarative syntax with attributes like `data-herb-action="refresh"`. - Dedicated browser dev tools provide visual indicators pinpointing exactly which template slots update on user interaction.
---
[15:02](https://www.youtube.com/watch?v=REtfdqtW3PY&t=902s) Under the Hood: The Slots Format and Schema Negotiation
Behind the scenes of that refresh interaction, the browser issues a standard HTTP `GET` request to the exact URL the user is currently visiting, while attaching a specialized `Accept` header requesting the Herb slots format. On the Rails side, the controller action remains entirely standard and empty:
```ruby def index end ```
Normally, Rails implicitly responds to this request with the HTML format. ReactionView hooks into this lifecycle by registering an implicit response format for slots. While the user's initial browser navigation receives standard HTML, any subsequent background request bearing the slot-specific `Accept` header is routed through the slot compiler instead.
The payload returned by this format is essentially a direct representation of the template's schema. If a template defines five slots, the response contains a compact payload indexing those five slots. Because the client-side runtime already possesses the identical schema, it simply reads the values keyed to slot zero, slot one, and so forth, patching them directly into their corresponding DOM positions.
| Request Type | Accept Header | Render Engine | Payload Received by Browser | |:--- |:--- |:--- |:--- | | **Initial Navigation** | `text/html` | Action View / Herb | Full HTML markup containing slot comment delimiters | | **Subsequent Action** | Herb Slots Accept Header | ReactionView Slots Formatter | Lightweight schema payload containing only dynamic slot values |
- Controller actions require zero custom boilerplate; Rails routes and responds implicitly via content negotiation. - ReactionView introduces a dedicated slots format alongside standard Rails formats. - Payloads omit repetitive HTML tags, sending only the raw data mapped to specific slot indices. - The client runtime uses its local copy of the template schema to bind incoming slot values to their matching DOM comment boundaries.
---
[16:03](https://www.youtube.com/watch?v=REtfdqtW3PY&t=963s) Client-Side UI State Without Stimulus Boilerplate
While slot refreshing provides efficient server round-trips, modern applications demand immediate UI state—ephemeral values that govern visibility, toggles, and modal states without touching a database. Marco emphasizes his architectural philosophy: "not every UI interaction itself needs to go and do a server round trip." Traditionally, Rails developers reach for Stimulus, creating a custom JavaScript controller and wiring up targets, values, and actions just to toggle an element.
Herb bypasses this boilerplate by letting developers write pure ERB. Drawing inspiration from strict locals syntax, Herb introduces a template directive for state:
```erb <%# herb:state, show: false %> ```
Here, `show` is defined as a reactive client-side boolean state initialized to `false`. Inside the template, developers write standard Ruby conditional checks like `<% if show? %>`. Because the Herb compiler understands that `show` is a declared state variable, it recognizes what DOM elements depend on it.
To manipulate that state, developers attach declarative attributes to their HTML elements:
```html <button data-herb-set="show = true">Show Details</button> ```
Because the target element is a `<button>`, Herb implicitly binds to the `click` event, matching the ergonomic shortcuts popularized by Stimulus. For boolean values, a toggle helper can alternate the state between `true` and `false`. The entire conditional branch appears and disappears purely on the client side without executing a single network request or requiring a dedicated JavaScript file.
- Ephemeral UI interactions (toggles, accordions, dialogs) remain entirely on the client, avoiding unnecessary server round-trips. - The `herb:state` directive defines local reactive variables using syntax inspired by Rails strict locals. - Standard Ruby predicate methods and conditionals (`if show?`) are understood by the compiler and converted into client-side visibility toggles. - The `data-herb-set` attribute enables declarative state modification directly in HTML, supporting explicit values and toggle actions.
---
[17:48](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1068s) Two-Way Data Binding Without JavaScript or Ruby Wasm
Herb extends this declarative model to full two-way data binding for form inputs. Because Herb parses both HTML and Ruby simultaneously, it can inspect form elements directly. When an input's `value` attribute consists solely of an ERB expression linked to a declared state—such as `<input value="<%= message %>">` where `message` is defined via `<%# herb:state, message: "" %>`—the compiler binds the input to the state during compilation.
```erb <%# herb:state, message: "" %>
<input type="text" value="<%= message %>"> <p>Character count: <%= message.length %></p> ```
As the user types into the text input, the runtime updates the `message` state on every keystroke. Herb's reactivity model recognizes that `<%= message.length %>` depends on that state and recalculates the length instantly on the client.
Marco clarifies that this does not involve heavy machinery: there is no custom JavaScript written by the developer, no Ruby-to-JavaScript transpilation (such as Opal), and no Ruby WebAssembly virtual machine bundled into the client. Instead, ReactionView analyzes the abstract syntax tree (AST) of the ERB template, identifies the limited subset of standard Ruby methods commonly used in view layers (like `.length` or string operations), and compiles those operations directly into an efficient client-side activity model.
- Declaring a state variable and assigning it to an `<input value="...">` creates an automatic two-way binding during compilation. - Dependent Ruby view expressions update reactively in the DOM as input values change. - The system does **not** rely on client-side Ruby WebAssembly runtimes or Ruby-to-JS transpilers. - ReactionView statically translates the common subset of Ruby view operations into optimized client-side reactivity at compile time.
---
[19:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1178s) Hybrid Reactivity: Automatic Server Fallbacks
When view logic exceeds the capabilities of client-side static analysis—such as executing complex domain logic or querying an ActiveRecord database—Herb seamlessly falls back to the server. Marco illustrates this with a dynamic search query:
```erb <%# herb:state, q: "" %>
<input type="text" value="<%= q %>">
<div> <%= Topic.search(q) %> </div> ```
The compiler identifies that `q` is a reactive client-side state, and it detects that the ERB tag invoking `Topic.search(q)` directly depends on that state. However, because `Topic.search` represents a database query that cannot run inside the browser, the compiler wires the dependency to execute a background network request whenever `q` changes.
The server evaluates the query, serializes the updated results into the slots format, and returns the payload to the browser. The client runtime then patches the returned values directly into the appropriate slot in the DOM.
Marco reflects on the elegance of this workflow: "And if you look at this, that's just how you write ERB. I've been writing ERB the whole time, but it's just reactive now, which is super cool." Developers get fine-grained, reactive UI updates without needing to configure Turbo Frames, create custom Turbo Stream responses, synchronize DOM element IDs, or write dedicated controller actions and routes. The server and client negotiate updates transparently through the shared template schema.
- Complex expressions and database queries automatically trigger smart background server requests. - The client runtime handles data transport, slot decoding, and DOM patching automatically. - Eliminates the manual wiring typically required for Turbo Frames, Turbo Streams, and dedicated search endpoints. - Developers write standard, idiomatic ERB while Herb infuses it with full client-server reactivity.
---
[21:22](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1282s) Deferring Slow Queries with the `<Async>` Tag
Marco transitions to another common performance hurdle in Rails views: lazy loading slow database queries to avoid blocking the initial page render. In standard Rails development, deferring an expensive query usually requires extracting the markup into a dedicated partial, registering a separate route, and wrapping the content in a `<turbo-frame>` configured with a `src` attribute.
Herb streamlines this process by introducing a built-in `<Async>` component tag with a capital "A", adopting component ergonomics familiar to developers from modern UI ecosystems like React:
```html <Async> <%= SlowMetric.calculate_expensive_stats %> </Async> ```
Instead of requiring separate endpoints and fragment extractions, the `<Async>` tag allows developers to defer expensive view segments inline, treating asynchronous rendering as a first-class feature of the template itself.
- Eliminates the need to extract lazy-loaded markup into separate partials and standalone controller routes. - The capitalized `<Async>` tag brings modern component ergonomics directly into standard ERB views. - Deferring slow queries and metrics is declared inline at the template layer.
[21:57](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1317s) Async and Lazy Declarative Rendering
Marco Roth explains how the Herb compiler unlocks asynchronous and lazy view rendering directly within templates. When a developer marks a template section as async, the compiler recognizes the structure, identifies the fallback skeleton, and renders that lightweight placeholder immediately on the initial request. As soon as the initial HTML arrives in the client browser, the client runtime identifies the async boundary, reaches back out to the server in the background, fetches the actual data payload, and seamlessly swaps it into place.
```mermaid flowchart TD accTitle: Declarative Async and Lazy View Pipeline accDescr: Diagram showing initial skeleton rendering on the server followed by client-side asynchronous fetching or viewport intersection triggers.
subgraph InitialRender["1. Initial Server Render"] A["Compiler parses ERB template"] --> B["Detects async or lazy block"] B --> C["Renders lightweight fallback skeleton"] end
subgraph ClientDelivery["2. Client Browser"] C --> D["HTML displayed instantly in browser"] D --> E{"Trigger Condition"} E -->|"Immediate Async"| F["Background fetch to server"] E -->|"Lazy (Viewport)"| G["Waits until scrolled into view"] G --> F end
subgraph ServerHydration["3. Server Data Fetch"] F --> H["Action View server responds with payload"] H --> I["DOM seamlessly swaps skeleton for content"] end ```
The exact same mental model extends to lazy evaluation. Rather than kicking off a network request the moment the page mounts, a lazy block pauses until the element actually enters the user's viewport. Marco steps through a live demo: as the user loads the page, initial content is immediately present, and only when scrolling all the way to the bottom does the lazy block begin fetching and streaming in the remaining values. Roth emphasizes that this is achieved through pure declarative state in ERB. He notes candidly that he is completely open to community feedback regarding syntax and naming: "I'm open to feedback about naming this because this is just the way I came up with, but if you have any more idea about syntax and naming on this, I'm open to feedback here."
[23:06](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1386s) Building Rich UIs in ERB: The Zero-JavaScript Music Player
To demonstrate what this architecture achieves in practice without writing frontend glue code, Roth unveils a fully functioning music player and library interface built entirely in ERB. When a user clicks through the catalog, details appear instantaneously. Skeletons handle in-flight state gracefully, and users can play back audio tracks, pause, toggle controls, and close panels without lag.
Roth highlights that the entire interactive music player application runs on roughly 100 lines of ERB with absolutely zero custom JavaScript. By shifting structural awareness into the compilation step, standard Rails templates can replicate the polished, responsive experiences that traditionally required dedicated Single Page Application (SPA) frameworks or heavy client scripts.
[23:42](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1422s) Optimistic UI and State Rollbacks
Roth advances to one of the most demanding problems in web interfaces: optimistic UI. Because the Herb compiler understands the complete template tree ahead of time, that markup representation can be pushed down to the client runtime to perform real client-side rendering. Demonstrating a real-time chat application, Roth shows messages appearing in the stream instantly upon submission. If a network transmission fails, the client marks the failure immediately, allowing users to hit retry and resend without losing their input.
Editing and deleting messages also happen locally without a server round trip because toggling states and hiding or showing nodes are handled directly in the browser runtime. When a user stars a message, the action renders instantly in the UI while queuing a background HTTP request. If that request fails, the runtime references an internal state ledger and rolls back to the last known good state. By keeping a comprehensive log of all historical state transitions, the system can deterministically travel backward and forward across UI mutations.
- Chat messages render on the client immediately without waiting for server confirmation. - Destructive and update actions, such as editing or deleting, manipulate local DOM state directly without network round trips. - Network requests are placed into an asynchronous queue. - If an operation fails, the runtime rewinds the UI to the last confirmed good state using an event log of state transitions.
[24:46](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1486s) Reaction View Runtime and Low-Level JavaScript Interoperability
While Roth underscores that developers can build remarkably ambitious, interactive applications using pure ERB, he acknowledges that complex real-world projects frequently require integration with NPM packages or custom DOM event listeners. To bridge this gap, Reaction View exposes a low-level JavaScript API that interfaces directly with the client runtime.
Getting started with Reaction View requires minimal ceremony: developers simply add the `reaction_view` gem to their Gemfile and run the included install generator. Full documentation is hosted at `reactionview.dev`, and the source code and interactive demonstrations are available open source at `demo.reactionview.dev` and on GitHub.
[25:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1538s) Scoped Styles and Scripts: Rust Tooling in the Rails View Layer
Looking toward future developments, Roth addresses persistent friction points in frontend Rails development: managing scoped CSS and component-level JavaScript without introducing heavy toolchains. To prevent style bleed across view templates, Reaction View introduces `<style scoped>`, enabling developers to embed component-specific style tags directly inside ERB partials. Under the hood, preprocessing is handled by `lightningcss-ruby`, a newly packaged native gem binding the Rust-based Lightning CSS engine to parse, scope, and minify styles on the fly without leaking outside the partial boundary.
Roth envisions an equivalent pattern for JavaScript using `<script scoped>`. To power this without introducing Node.js into the Rails deployment pipeline, the team created `oxc-ruby`. Built upon Oxc—the high-performance Rust JavaScript tooling suite powering modern frontend build tools like Vite and Rollup—the gem allows Rails developers to write modern TypeScript or JSX directly within their views. It compiles and bundles code natively while staying fully compatible with Rails import maps.
```mermaid flowchart LR accTitle: High-Performance Rust Tooling in Rails Views accDescr: Flowchart illustrating how ERB partials leverage Rust-backed gems to compile scoped styles and scripts without Node.js.
subgraph ERB["ERB Template"] CSS["<style scoped>"] JS["<script scoped> (Future)"] end
subgraph RustTooling["Rust Gems (No Node.js Required)"] LCSS["lightningcss-ruby\n(Lightning CSS)"] OXC["oxc-ruby\n(Oxc Engine)"] end
subgraph Outputs["Engine Output"] OutCSS["Scoped, isolated partial CSS"] OutJS["Bundled TypeScript / JSX\n(Import Map Compatible)"] end
CSS --> LCSS --> OutCSS JS --> OXC --> OutJS ```
Beyond styling and scripting, Roth points to several major frontiers unlocked by having a DOM-aware view layer: - **True Offline Mode:** Because full template markup structures are precompiled and pushed down to the client runtime, applications can run fully offline and synchronize later. - **Side-by-Side React Components:** Native ERB templates and React components can coexist seamlessly in the same view trees. - **Encapsulated Gem Components:** Third-party Ruby gems can package and distribute full-stack components that encompass coordinated server-side and client-side behaviors, rendered natively through the Herb engine.
[27:29](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1649s) The Future of Full-Stack Rails and hotwire Evolution
Reflecting on the overarching vision, Roth expresses deep satisfaction with where Herb and Reaction View are heading, noting that developers can once again write classic Ruby and ERB views while delivering fully reactive, dynamic interfaces. Rather than forcing teams into an all-or-nothing dichotomy between backend monolithic views and JavaScript-heavy SPAs, this model offers a continuous spectrum: teams can adopt as much or as little JavaScript as their product requires without abandoning full-stack Rails idioms.
Roth notes that Reaction View represents what he always envisioned StimulusReflex could become, but implemented cleanly from the view compiler up. By expanding Hotwire's capabilities while preserving the simplicity Rails has championed for two decades, developers gain the freedom to introduce React or custom client components when strictly necessary without sacrificing the cohesive developer experience of Rails. He emphasizes that bringing Herb into Rails 8.2 establishes the bedrock, but its long-term success depends entirely on community collaboration, real-world stress testing, and honest feedback from skeptics and early adopters alike.
[29:25](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1765s) Acknowledgements and Community Links
Roth wraps up his Rails World presentation by expressing gratitude to the open-source community. He thanks the contributors who submitted pull requests, triaged bugs, tested experimental features, and shaped the projects into production-ready software. He also thanks the sponsors whose financial support sustained his open-source work across the toolchain.
He encourages attendees to try out the gems, star the Herb and Reaction View repositories on GitHub, submit issues, and explore the updated documentation and live application examples available at `reactionview.dev` and `herb-tools.dev`.
## Notable quotes > [00:00](https://www.youtube.com/watch?v=REtfdqtW3PY&t=0s) Herb has full semantic and structural understanding of your ERB templates.
> [00:00](https://www.youtube.com/watch?v=REtfdqtW3PY&t=0s) Now its engine renders and compiles ERB views inside Action View itself.
> [07:17](https://www.youtube.com/watch?v=REtfdqtW3PY&t=437s) everything a client-side framework does, you can now get without having to adopt one. — Marco Roth
> [13:39](https://www.youtube.com/watch?v=REtfdqtW3PY&t=819s) The entire system is built upon just four fundamental structural primitives: outputs, slots, collections, and conditionals. — Marco Roth
> [15:02](https://www.youtube.com/watch?v=REtfdqtW3PY&t=902s) The payload returned by this format is essentially a direct representation of the template's schema. — Marco Roth
> [16:03](https://www.youtube.com/watch?v=REtfdqtW3PY&t=963s) not every UI interaction itself needs to go and do a server round trip. — Marco Roth
> [19:38](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1178s) And if you look at this, that's just how you write ERB. I've been writing ERB the whole time, but it's just reactive now, which is super cool. — Marco Roth
> [21:57](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1317s) I'm open to feedback about naming this because this is just the way I came up with, but if you have any more idea about syntax and naming on this, I'm open to feedback here. — Marco Roth
> [23:06](https://www.youtube.com/watch?v=REtfdqtW3PY&t=1386s) By shifting structural awareness into the compilation step, standard Rails templates can replicate the polished, responsive experiences that traditionally required dedicated Single Page Application (SPA) frameworks or heavy client scripts. — Marco Roth
## Mentioned Marco Roth, Ruby on Rails, Rails 8.2, Rails World 2026, Herb, ERB, Action View, Hotwire, Turbo, Stimulus, StimulusReflex, Phoenix LiveView, Laravel Livewire, Inertia.js, Reaction View, ActiveSupport::Notifications, ActiveRecord, Solid Queue, Solid Cache, Solid Cable, SQLite, Ruby Wasm, Opal, React, lightningcss-ruby, Lightning CSS, oxc-ruby, Oxc, Vite, Rollup, Shopify
## Video description Rails 8.2 ships with Herb, a toolchain that understands your HTML and Ruby together. At Rails World 2026, Marco Roth explains what Herb is and what it adds to the Rails view layer.
Herb has full semantic and structural understanding of your ERB templates. That makes it possible to build a linter, a formatter, a language server, browser dev tools, and a dev server that updates your views and partials in real time without a page refresh. Now its engine renders and compiles ERB views inside Action View itself. Marco walks through what ships in Rails 8.2 and what an HTML-aware view layer makes possible next.
Links: Presentation: https://speakerdeck.com/marcoroth/herb-in-rails-8-dot-2-your-erb-views-now-html-aware-at-rails-world-2026-austin-texas https://herb-tools.dev https://reactionview.dev https://github.com/marcoroth/herb https://github.com/marcoroth/reactionview https://herb-tools.dev/blog/whats-new-in-herb-v0-11
***
Thank you Shopify for sponsoring the editing and post-production of these videos. Check out insights from the Engineering team at: https://shopify.engineering/
#RailsWorld #RubyOnRails #Rails82 #Herb #ERB #Hotwire
[Source](https://www.youtube.com/watch?v=REtfdqtW3PY)