RoR Turbo and Hotwire

Hotwire is a collection of tools that makes Rails apps feel fast and interactive without writing JavaScript. It sends HTML over the wire instead of JSON and uses small JavaScript libraries to update only the parts of the page that changed. The result is a snappy, app-like experience built entirely in Rails, Ruby, and minimal JavaScript.

Hotwire Components

Hotwire
  |
  +-- Turbo Drive      ← makes page navigation feel instant
  |
  +-- Turbo Frames     ← update specific page sections without full reload
  |
  +-- Turbo Streams    ← push real-time HTML updates from server to browser
  |
  +-- Stimulus         ← small JavaScript for behaviour that HTML cannot do alone

Turbo Drive — Instant Page Navigation

Turbo Drive intercepts every link click and form submission. Instead of a full page reload, it fetches only the new page's <body> and swaps it in. Your navigation, sidebar, and header never reload. Pages feel instant.

No code required — Turbo Drive is on by default in Rails 7+.

Opt out for a specific link:
  <%= link_to "Download PDF", report_path, data: { turbo: false } %>

Turbo Frames — Update Page Sections

A Turbo Frame is a named region of a page. When a link or form inside a frame fires, only that frame updates — the rest of the page stays untouched.

Wrap a section in a turbo-frame tag:

app/views/articles/index.html.erb

<%= turbo_frame_tag "articles-list" do %>
  <% @articles.each do |article| %>
    <div><%= article.title %></div>
  <% end %>

  <%= link_to "Next Page", articles_path(page: @page + 1) %>
<% end %>

When the user clicks "Next Page," Rails returns the next page's HTML. Turbo finds the matching turbo_frame_tag "articles-list" in the response and swaps only that section — no full page reload, no JavaScript code written.

Turbo Frames for Inline Editing

Show page:
app/views/articles/show.html.erb

<%= turbo_frame_tag @article do %>
  <h1><%= @article.title %></h1>
  <p><%= @article.body %></p>
  <%= link_to "Edit Inline", edit_article_path(@article) %>
<% end %>

Edit page:
app/views/articles/edit.html.erb

<%= turbo_frame_tag @article do %>
  <%= form_with model: @article do |f| %>
    <%= f.text_field :title %>
    <%= f.text_area :body %>
    <%= f.submit "Save" %>
  <% end %>
<% end %>

When the user clicks "Edit Inline," only the turbo frame swaps to the edit form. The rest of the page stays in place. After saving, the frame swaps back to the show view. Zero custom JavaScript.

Turbo Streams — Real-Time Updates

Turbo Streams push HTML updates from the server to the browser after actions. They can append, prepend, replace, remove, or update any element on the page by its DOM id.

app/controllers/comments_controller.rb

def create
  @comment = @article.comments.build(comment_params)
  @comment.user = current_user

  respond_to do |format|
    if @comment.save
      format.turbo_stream   ← renders the turbo_stream view
      format.html { redirect_to @article }
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end
app/views/comments/create.turbo_stream.erb

<%= turbo_stream.append "comments-list" do %>
  <%= render @comment %>
<% end %>

<%= turbo_stream.replace "comment-form" do %>
  <%= render "form", article: @article, comment: Comment.new %>
<% end %>

The user submits a comment. Rails saves it, then sends back two Turbo Stream actions: append the new comment to the list, and reset the comment form. No page reload. No polling. No WebSocket setup. It just works.

Turbo Stream Actions

turbo_stream.append "dom-id" do ... end       ← add inside element, at end
turbo_stream.prepend "dom-id" do ... end      ← add inside element, at start
turbo_stream.replace "dom-id" do ... end      ← swap the element
turbo_stream.update "dom-id" do ... end       ← update element's content
turbo_stream.remove "dom-id"                  ← delete the element
turbo_stream.before "dom-id" do ... end       ← add before the element
turbo_stream.after "dom-id" do ... end        ← add after the element

Stimulus — JavaScript for Behaviour

Stimulus handles interactions that HTML cannot express — like toggling menus, adding keyboard shortcuts, or connecting to browser APIs. Stimulus controllers are small JavaScript classes that attach to HTML elements via data attributes:

app/javascript/controllers/dropdown_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["menu"]

  toggle() {
    this.menuTarget.classList.toggle("hidden")
  }
}
In your HTML:
<div data-controller="dropdown">
  <button data-action="click->dropdown#toggle">
    Menu ▼
  </button>
  <ul data-dropdown-target="menu" class="hidden">
    <li><a href="/profile">Profile</a></li>
    <li><a href="/settings">Settings</a></li>
  </ul>
</div>

Hotwire vs React/Vue

FactorHotwireReact / Vue
JavaScript writtenVery littleA lot
ViewsERB (server-side)JSX / components (client-side)
API neededNoYes (JSON API)
Learning curveLow — standard RailsHigh — full JS framework
Best forStandard web apps with some interactivityHighly interactive single-page apps

Hotwire fits 80% of web application needs. Use it to add interactivity to your Rails app without abandoning the productivity of server-side Rails development. Reach for React or Vue only when you need a highly stateful, complex frontend experience.

Leave a Comment

Your email address will not be published. Required fields are marked *