Skip to content

Reactive views for the HTML+ERB you already have.

You get what a client-side framework gives you, without adopting one.

  • No new syntax
  • No client framework
  • No API layer
  • No build step
ReActionView

app/views/messages/index.html.erb

<%# herb:slots %>
<%# herb:state (composing: false) %>

<button data-herb-toggle="composing">New message</button>

<% if composing %>
  <form action="/messages" method="post">
    <input name="message[body]">
  </form>
<% end %>

The button flips a state the browser owns, so the form appears without a page load and the list above it stays exactly where it was.

You write it all in the template.

Declare state, change it from an attribute, read it anywhere in the same file. When a change needs the database, the controller you already have handles it, and only the parts that changed get updated.

Fine-grained reactivity

Declare a state at the top of the template and read it anywhere below. Change it, and every spot that reads it updates, down to a single value, an attribute or one row of a list. Nothing else on the page is touched, so open menus stay open, inputs keep what you typed, and the scroll position does not jump.

<%# herb:state (draft: "") %>

<textarea><%= draft %></textarea>
<p><%= draft.length %> characters</p>

When the server has to answer

Sorting and filtering need the database, and the browser cannot run Ruby. So the client requests the same URL again, your controller runs like it always does, and herb_state tells you what the browser currently holds. You render the same template, and only the values that changed go back.

GET /messages?format=slots
Accept: application/vnd.herb.slots+json
Herb-State: {"order": "newest"}

# app/controllers/messages_controller.rb
direction = herb_state("order", "oldest")
@messages = Message.order(created_at: direction)

Mistakes show up while you work

A mismatched tag, or a <div> inside a <p>, shows up in the browser with the file and the line. In tests it fails instead of shipping. The panel in the corner tells you which template rendered an element, how long it took, and what it queried.

✘ InvalidNestingError
  Block element <div> cannot be nested
  inside <p> at line 4

  app/views/pages/nesting.html.erb:4:3

Render stack
  pages/nesting.html.erb:4:3
  layouts/application.html.erb

Works with Hotwire

Turbo still drives navigation, and Stimulus is still the place for behavior that is more than writing a value. Markup arriving from a Turbo visit, frame or stream is picked up on its own, and a Stimulus controller can read and write the same states your template declares.

Stimulus and Turbo →

Built on Herb

ReActionView renders with Herb::Engine, which reads the HTML and the Ruby in a template as one language. Every bit of syntax on this page is documented over in the Herb language reference.

Herb Language reference →

Supported by 36 people and companies.

Herb is an independent open source project, and it relies on the companies that use it to keep going. If your team depends on Herb, consider sponsoring the project.

  • wbotelhos
  • jespr
  • DRBragg
  • peterberkenbosch
  • kcdragon
  • ChaelCodes
  • larouxn
  • lxxxvi
  • williamkennedy
  • janko
  • irinanazarova
  • gurgeous
  • kdaigle
  • rafaelfranca
  • cb341
  • myabc
  • ajaya
  • doolin
  • sobstel
  • apiguy
  • rosa
  • tysongach
  • palkan
  • itsameandrea
  • markokajzer
  • chris-biagini

Install it in your Rails app.

Two commands, and you are running it. Ruby 3.2 or newer, Rails 7.0 or newer.

$ bundle add reactionview
$ bin/rails generate reactionview:install

Reactive templates are experimental. They need ReActionView ^0.6.0 and Herb ^0.11.0, and their markup, payload and JavaScript API may change between releases.

Released under the MIT License.