ワンクリックで
hotwire-turbo
Best practices for using Hotwire Turbo to create reactive applications
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Best practices for using Hotwire Turbo to create reactive applications
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | hotwire-turbo |
| description | Best practices for using Hotwire Turbo to create reactive applications |
Rule updated on 12/15/2025 to Turbo version 8.0.18.
Hotwire consists of three main components, each suited for different use cases. Here's when to use each.
For full reference see https://turbo.hotwired.dev/
When to use:
How it works: Intercepts link clicks and form submissions, fetches the new page via AJAX, and swaps the <body> content while keeping the <head> intact.
Best practices:
data-turbo="false" to disable for specific links/forms (e.g., file downloads, external links)data-turbo-method="delete" for non-GET requests from links<%= link_to "Delete", invoice_path(@invoice), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>
When to use:
How it works: Instead of replacing the entire <body>, morphing intelligently diffs the current DOM against the new HTML and applies only the necessary changes. This preserves:
Enabling morphing:
<%# In your layout or page head %>
<meta name="turbo-refresh-method" content="morph">
<%# Optionally preserve scroll position %>
<meta name="turbo-refresh-scroll" content="preserve">
Per-element control:
<%# Keep an element from being morphed (preserves exact state) %>
<div data-turbo-permanent id="chat-messages">
<!-- Content preserved across page updates -->
</div>
When NOT to use morphing:
When to use:
How it works: Wraps a section of the page in a <turbo-frame> tag. When a link or form inside the frame is activated, only that frame's content is replaced.
Best practices:
<!-- index.html.erb -->
<%= turbo_frame_tag @invoice do %>
<div class="invoice-row">
<%= @invoice.number %>
<%= link_to "Edit", edit_invoice_path(@invoice) %>
</div>
<% end %>
<!-- edit.html.erb -->
<%= turbo_frame_tag @invoice do %>
<%= form_with model: @invoice do |f| %>
<!-- form fields -->
<% end %>
<% end %>
turbo_frame_tag with a model — Rails generates consistent IDs (invoice_123)data-turbo-frame="_top" for full-page navigationsrc and loading: "lazy":
<%= turbo_frame_tag "comments", src: comments_path, loading: "lazy" do %>
<p>Loading comments...</p>
<% end %>
data-turbo-frame="frame_id"When to use:
How it works: Returns <turbo-stream> elements that specify actions (append, prepend, replace, update, remove, before, after, morph, refresh) and target DOM elements by ID.
Best practices:
# Controller
def create
@invoice = current_user.invoices.build(invoice_params)
respond_to do |format|
if @invoice.save
format.turbo_stream # Renders create.turbo_stream.erb
format.html { redirect_to @invoice }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
<!-- create.turbo_stream.erb -->
<%= turbo_stream.prepend "invoices", @invoice %>
<%= turbo_stream.update "invoice_count", Invoice.count %>
<%= turbo_stream.remove "empty_state" %>
Stream Actions:
| Action | Description |
|---|---|
append | Add to end of target container |
prepend | Add to beginning of target container |
replace | Replace the entire target element |
update | Replace only the content (innerHTML) |
remove | Remove the target element |
before | Insert before the target |
after | Insert after the target |
morph | Morph the target element (intelligent diff) |
refresh | Trigger a full page refresh (optionally with morph) |
Morph stream action example:
<%# Smoothly update a section without replacing it entirely %>
<%= turbo_stream.morph "invoice_#{@invoice.id}", partial: "invoices/invoice", locals: { invoice: @invoice } %>
| Scenario | Solution |
|---|---|
| Standard navigation | Turbo Drive (automatic) |
| Edit form inline | Turbo Frame |
| Load section lazily | Turbo Frame with src |
| Add item to list | Turbo Stream append/prepend |
| Update multiple areas | Turbo Stream |
| Real-time via WebSocket | Turbo Stream over ActionCable |
| Delete from list | Turbo Stream remove |
| Modal/slideout | Turbo Frame targeting a container |
| Preserve scroll on refresh | Morphing with turbo-refresh |
| Smooth inline update | Turbo Stream morph |
| Update without losing focus | Morphing |
dom_id(@invoice)) for reliable targetinghtml format fallback for non-Turbo requests