con un clic
hotwire-turbo
Best practices for using Hotwire Turbo to create reactive applications
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Best practices for using Hotwire Turbo to create reactive applications
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional 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