Claude Code Plugins

Community-maintained marketplace

Feedback

hotwire-turbo

@sandnap/easy_notes
6
0

Best practices for using Hotwire Turbo to create reactive applications

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name hotwire-turbo
description Best practices for using Hotwire Turbo to create reactive applications

Hotwire Best Practices for 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/

Turbo Drive (Default Page Navigation)

When to use:

  • Standard page-to-page navigation (it's on by default)
  • Full page updates where you want faster transitions without a full browser reload
  • Simple CRUD operations where you're redirecting after an action

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:

  • It's automatic—you get it for free in Rails 8
  • Use data-turbo="false" to disable for specific links/forms (e.g., file downloads, external links)
  • Use 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?" } %>

Turbo 8 Morphing (Smooth Page Refreshes)

When to use:

  • Form submissions where you want to preserve scroll position
  • Updates that should feel seamless without visible "flash"
  • Pages with user input that shouldn't be lost during refresh
  • Maintaining focus state and CSS transitions during updates

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:

  • Scroll position
  • Form input values
  • Focus state
  • Active CSS transitions/animations

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 you want a clear visual transition between pages
  • When the page structure changes dramatically
  • When you need to reset all page state

Turbo Frames (Partial Page Updates)

When to use:

  • Inline editing (edit-in-place forms)
  • Lazy loading content sections
  • Modal dialogs or slideovers
  • Tabbed interfaces
  • Pagination within a section
  • Any time you want to update a specific region without touching the rest

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 %>
  • Use turbo_frame_tag with a model — Rails generates consistent IDs (invoice_123)
  • Break out of frames with data-turbo-frame="_top" for full-page navigation
  • Lazy load with src and loading: "lazy":
    <%= turbo_frame_tag "comments", src: comments_path, loading: "lazy" do %>
      <p>Loading comments...</p>
    <% end %>
    
  • Target other frames with data-turbo-frame="frame_id"

Turbo Streams (Real-Time DOM Manipulation)

When to use:

  • Updating multiple parts of the page from a single action
  • Real-time updates via WebSockets (ActionCable)
  • Adding/removing items from lists without full refresh
  • Flash messages after form submissions
  • Counter/badge updates
  • Any scenario where you need surgical DOM updates

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 } %>

Decision Matrix

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

Key Principles

  1. Progressive Enhancement — Start with Turbo Drive, add Frames for scoped updates, then Streams for complex interactions
  2. Minimize JavaScript — Use Stimulus only when you need client-side behavior that can't be achieved with Turbo
  3. Semantic IDs — Use model-based IDs (dom_id(@invoice)) for reliable targeting
  4. Graceful Degradation — Always have an html format fallback for non-Turbo requests
  5. Keep Frames Small — Smaller frames = faster updates and easier maintenance
  6. Use Morphing for Polish — Enable morphing when smooth transitions matter (forms, live updates)