Skip to content
Back to skills

Hotwire Turbo

ASecurity

Best practices for using Hotwire Turbo to create reactive applications

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 9, 2026
developmentjavascriptjavarubyreactrails

Works with

  • cli

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill hotwire-turbo --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hotwire Turbo?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hotwire Turbo
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-hotwire-turbo/badge)](https://www.skillsdirectory.com/skills/majiayu000-hotwire-turbo)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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/](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

```erb
<%= 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:**

```erb
<%# 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:**

```erb
<%# 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:**

```erb
<!-- 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"`:
  ```erb
  <%= 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:**

```ruby
# 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
```

```erb
<!-- 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:**

```erb
<%# 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)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.