hotwire-turbo

Best practices for using Hotwire Turbo to create reactive applications

6 stars

Best use case

hotwire-turbo is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Best practices for using Hotwire Turbo to create reactive applications

Teams using hotwire-turbo should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/hotwire-turbo/SKILL.md --create-dirs "https://raw.githubusercontent.com/sandnap/easy_notes/main/.claude/skills/hotwire-turbo/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/hotwire-turbo/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How hotwire-turbo Compares

Feature / Agenthotwire-turboStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Best practices for using Hotwire Turbo to create reactive applications

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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)

Related Skills

stimulus

6
from sandnap/easy_notes

Best practices for using Stimulus controllers to add JavaScript behavior to HTML

nextjs-turbopack

144923
from affaan-m/everything-claude-code

Next.js 16+ and Turbopack — incremental bundling, FS caching, dev speed, and when to use Turbopack vs webpack.

DevelopmentClaude

turborepo-caching

31392
from sickn33/antigravity-awesome-skills

Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching.

turborepo

1864
from LeoYeAI/openclaw-master-skills

Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries. Use when user: configures tasks/workflows/pipelines, creates packages, sets up monorepo, shares code between apps, runs changed/affected packages, debugs cache, or has apps/packages directories.

turborepo

685
from openai/plugins

Turborepo expert guidance. Use when setting up or optimizing monorepo builds, configuring task caching, remote caching, parallel execution, or the --affected flag for incremental CI.

turbopack

685
from openai/plugins

Turbopack expert guidance. Use when configuring the Next.js bundler, optimizing HMR, debugging build issues, or understanding the Turbopack vs Webpack differences.

turborepo

509
from a5c-ai/babysitter

Turborepo configuration, caching, and pipeline optimization.

turbopack

509
from a5c-ai/babysitter

Turbopack configuration and Next.js integration.

turborepo-caching

242
from aiskillstore/marketplace

Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching.

turborepo

242
from aiskillstore/marketplace

Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries. Use when user: configures tasks/workflows/pipelines, creates packages, sets up monorepo, shares code between apps, runs changed/affected packages, debugs cache, or has apps/packages directories.

turbo

134
from smnandre/symfony-ux-skills

Hotwire Turbo for Symfony UX -- SPA-like speed with zero JavaScript. Covers Drive (full-page navigation), Frames (partial page sections), and Streams (multi-target updates). Use when building ajax navigation, lazy-loaded page sections, inline editing, pagination without reload, modals loaded from the server, flash messages via streams, real-time updates via Mercure/SSE, or multi-section page updates. Code triggers: turbo-frame, turbo-stream, data-turbo-frame, data-turbo, data-turbo-action, turbo-stream-source, TurboStreamResponse, <twig:Turbo:Frame>, <twig:Turbo:Stream:Append>, <twig:Turbo:Stream:Replace>, turbo:before-fetch-request. Also trigger when the user asks "how to update part of the page without reload", "how to make navigation feel like SPA", "how to lazy-load a section", "how to do inline editing", "how to push real-time updates from server", "how to use Mercure with Turbo". Do NOT trigger for client-side JS behavior (use stimulus), server-rendered reactive components (use live-component), or reusable static UI (use twig-component).

turborepo

118
from einverne/dotfiles

Guide for implementing Turborepo - a high-performance build system for JavaScript and TypeScript monorepos. Use when setting up monorepos, optimizing build performance, implementing task pipelines, configuring caching strategies, or orchestrating tasks across multiple packages.