Rails Frontend

Hotwire setup and decision guide.

Hotwire keeps Rails in charge of HTML while Turbo handles navigation and page updates, and Stimulus adds focused JavaScript where the browser needs behavior.

Setup

The Rails import map setup.

Gemfile

gem "turbo-rails"
gem "stimulus-rails"
gem "importmap-rails"

config/importmap.rb

pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin_all_from "app/javascript/controllers", under: "controllers"

app/javascript/application.js

import "@hotwired/turbo-rails"
import "controllers"

app/javascript/controllers/index.js

import { application } from "controllers/application"
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)

When to Use Each Part

Choose the smallest Hotwire tool that fits.

Turbo Drive

Use it when the HTML already belongs to Rails.

Best forUse for normal page navigation and form submissions where server-rendered HTML should feel faster.

SetupImport `@hotwired/turbo-rails`; Rails enables it for links and forms by default.

Use cautionDisable per element with `data-turbo="false"` when a third-party widget or redirect flow needs full browser behavior.

Turbo Frames

Use it when the HTML already belongs to Rails.

Best forUse when one region owns its own navigation, such as inline edit forms, paginated panels, modal bodies, or lazy-loaded sections.

SetupWrap the region in `turbo_frame_tag "frame_id"`; responses must include a matching frame.

Use cautionDo not add frames only because a Turbo Stream needs a target. A normal element with an id is enough for streams.

Turbo Streams over HTTP

Use it when the HTML already belongs to Rails.

Best forUse after create, update, and destroy actions when Rails should append, replace, remove, or refresh pieces of the page.

SetupRespond to `format.turbo_stream` in the controller or render a `.turbo_stream.erb` template.

Use cautionReturn HTML redirects for workflows where the user should land on a different page.

Turbo Streams over WebSocket

Use it when the HTML already belongs to Rails.

Best forUse for real-time updates from other users or background work, such as chats, notifications, dashboards, and job progress.

SetupUse `turbo_stream_from` in the view and model broadcasts or Action Cable broadcasts on the server.

Use cautionSkip the socket when the update only needs to answer the current user's form submission.

Stimulus

Use it when the HTML already belongs to Rails.

Best forUse for behavior that belongs in the browser: toggles, clipboard buttons, previews, keyboard shortcuts, validation hints, and small widgets.

SetupGenerate controllers in `app/javascript/controllers` and connect them with `data-controller`, `data-action`, and targets.

Use cautionDo not move business rules into Stimulus; keep durable decisions in Rails and use Stimulus to enhance the UI.

Examples

Copy-ready patterns.

Turbo Frame

<%= turbo_frame_tag "profile_card" do %>
  <%= link_to "Edit", edit_profile_path %>
<% end %>

HTTP Stream

respond_to do |format|
  format.turbo_stream { render turbo_stream: turbo_stream.append("messages", @message) }
  format.html { redirect_to messages_path }
end

Stimulus

<div data-controller="clipboard">
  <button data-action="clipboard#copy">Copy</button>
</div>

WebSocket Stream

<%= turbo_stream_from "room_1" %>
<div id="messages"></div>