Claude
Skills
Sign in
Back

rails-ai:hotwire

Included with Lifetime
$97 forever

Use when adding interactivity to Rails views - Hotwire Turbo (Drive, Frames, Streams, Morph) and Stimulus controllers

General

What this skill does


# Hotwire (Turbo + Stimulus)

Build fast, interactive, SPA-like experiences using server-rendered HTML with Hotwire. Turbo provides navigation and real-time updates without writing JavaScript. Stimulus enhances HTML with lightweight JavaScript controllers.

<when-to-use>
- Adding interactivity without heavy JavaScript frameworks
- Building real-time, SPA-like experiences with server-rendered HTML
- Implementing live updates, infinite scroll, or dynamic content
- Creating modals, inline editing, or interactive UI components
- Replacing traditional AJAX with modern, declarative patterns
</when-to-use>

<benefits>
- **SPA-Like Speed** - Turbo Drive accelerates navigation without full page reloads
- **Real-time Updates** - Turbo Streams deliver live changes via ActionCable
- **Progressive Enhancement** - Works without JavaScript, enhanced with it (TEAM RULE #13)
- **Simpler Architecture** - Server-rendered HTML reduces client-side complexity
- **Turbo Morph** - Intelligent DOM updates preserve scroll, focus, form state (TEAM RULE #7)
- **Less JavaScript** - Stimulus provides just enough JS for interactivity
</benefits>

<team-rules-enforcement>
**This skill enforces:**
- ✅ **Rule #5:** Turbo Morph by default (Frames only for modals, inline editing, pagination, tabs)
- ✅ **Rule #6:** Progressive enhancement (must work without JavaScript)

**Reject any requests to:**
- Use Turbo Frames everywhere (use Turbo Morph for general CRUD)
- Skip progressive enhancement (features that require JavaScript to function)
- Build non-functional UIs without JavaScript fallbacks
</team-rules-enforcement>

<verification-checklist>
Before completing Hotwire features:
- ✅ Works without JavaScript (progressive enhancement verified)
- ✅ Turbo Morph used for CRUD operations (not Frames)
- ✅ Turbo Frames only for: modals, inline editing, pagination, tabs
- ✅ Stimulus controllers clean up in disconnect()
- ✅ All interactive features tested
- ✅ All tests passing
</verification-checklist>

<standards>
- **TEAM RULE #7:** Prefer Turbo Morph over Turbo Frames/Stimulus for general CRUD
- **TEAM RULE #13:** Ensure progressive enhancement (works without JavaScript)
- Use Turbo Drive for automatic page acceleration
- Use Turbo Morph for list updates and CRUD operations (preserves state)
- Use Turbo Frames ONLY for: modals, inline editing, tabs, pagination, lazy loading
- Use Turbo Streams for real-time updates via ActionCable
- Use Stimulus for client-side interactions (dropdowns, character counters, dynamic forms)
- Always clean up in Stimulus disconnect() to prevent memory leaks
- Test with JavaScript disabled to verify progressive enhancement
</standards>

---

## Hotwire Turbo

Turbo provides fast, SPA-like navigation and real-time updates using server-rendered HTML. Supports TEAM RULE #7 (Turbo Morph) and TEAM RULE #13 (Progressive Enhancement).

### TEAM RULE #7: Prefer Turbo Morph over Turbo Frames/Stimulus

✅ **DEFAULT APPROACH:** Use Turbo Morph (page refresh with morphing) with standard Rails controllers
✅ **ALLOW Turbo Frames ONLY for:** Modals, inline editing, tabs, pagination
❌ **AVOID:** Turbo Frames for general list updates, custom Stimulus controllers for basic CRUD

**Why Turbo Morph?** Preserves scroll position, focus, form state, and video playback. Works with stock Rails scaffolds. Simpler than Frames/Stimulus in 90% of cases.

### Turbo Drive

<pattern name="turbo-drive-basics">
<description>Automatic page acceleration with Turbo Drive</description>

Turbo Drive intercepts links and forms automatically. Control with `data` attributes:

```erb
<%# Disable Turbo for specific links %>
<%= link_to "Download PDF", pdf_path, data: { turbo: false } %>

<%# Replace without history %>
<%= link_to "Dismiss", dismiss_path, data: { turbo_action: "replace" } %>
```

</pattern>

### Turbo Morphing (Page Refresh) - PREFERRED

**Use Turbo Morph by default with standard Rails controllers.** Morphing intelligently updates only changed DOM elements while preserving scroll position, focus, form state, and media playback.

<pattern name="enable-morphing-layout">
<description>Enable Turbo Morph in your layout (one-time setup)</description>

```erb
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<head>
  <title><%= content_for?(:title) ? yield(:title) : "App" %></title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>

  <%# Enable Turbo Morph for page refreshes %>
  <meta name="turbo-refresh-method" content="morph">
  <meta name="turbo-refresh-scroll" content="preserve">
</head>
<body>
  <%= yield %>
</body>
</html>
```

**That's it!** Standard Rails controllers now work with morphing. No custom JavaScript needed.

**Reference:** [Turbo Page Refreshes Documentation](https://turbo.hotwired.dev/handbook/page_refreshes)
</pattern>

<pattern name="standard-rails-crud-with-morph">
<description>Standard Rails CRUD works automatically with Turbo Morph</description>

**Controller (stock Rails scaffold):**

```ruby
class FeedbacksController < ApplicationController
  def index
    @feedbacks = Feedback.all
  end

  def create
    @feedback = Feedback.new(feedback_params)
    if @feedback.save
      redirect_to feedbacks_path, notice: "Feedback created"
    else
      render :new, status: :unprocessable_entity
    end
  end

  def update
    if @feedback.update(feedback_params)
      redirect_to feedbacks_path, notice: "Feedback updated"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @feedback.destroy
    redirect_to feedbacks_path, notice: "Feedback deleted"
  end
end
```

**View (standard Rails):**

```erb
<%# app/views/feedbacks/index.html.erb %>
<h1>Feedbacks</h1>
<%= link_to "New Feedback", new_feedback_path, class: "btn btn-primary" %>

<div id="feedbacks">
  <% @feedbacks.each do |feedback| %>
    <%= render feedback %>
  <% end %>
</div>

<%# app/views/feedbacks/_feedback.html.erb %>
<div id="<%= dom_id(feedback) %>" class="card">
  <h3><%= feedback.content %></h3>
  <div class="actions">
    <%= link_to "Edit", edit_feedback_path(feedback), class: "btn btn-sm" %>
    <%= button_to "Delete", feedback_path(feedback), method: :delete,
                  class: "btn btn-sm btn-error",
                  form: { data: { turbo_confirm: "Are you sure?" } } %>
  </div>
</div>
```

**What happens:** Create/update/delete triggers redirect → Turbo intercepts → morphs only changed elements → scroll/focus preserved. No custom code needed!
</pattern>

<pattern name="permanent-elements-morph">
<description>Prevent specific elements from morphing with data-turbo-permanent</description>

```erb
<%# Flash messages persist during morphing %>
<div id="flash-messages" data-turbo-permanent>
  <% flash.each do |type, message| %>
    <div class="alert alert-<%= type %>"><%= message %></div>
  <% end %>
</div>

<%# Video/audio won't restart on page morph %>
<video id="tutorial" data-turbo-permanent src="tutorial.mp4" controls></video>

<%# Form preserves input focus during live updates %>
<%= form_with model: @feedback, id: "feedback-form",
              data: { turbo_permanent: true } do |form| %>
  <%= form.text_area :content %>
  <%= form.submit %>
<% end %>
```

**Use cases:** Flash messages, video/audio players, forms with unsaved input, chat messages being typed.
</pattern>

<pattern name="broadcast-refresh-realtime">
<description>Real-time updates with broadcasts_refreshes (morphs all connected clients)</description>

```ruby
# Model broadcasts page refresh to all subscribers (Rails 8+)
class Feedback < ApplicationRecord
  broadcasts_refreshes
end
```

```erb
<%# View subscribes to stream - morphs when model changes %>
<%= turbo_stream_from @feedback %>

<div id="feedbacks">
  <% @feedbacks.each do |feedback| %>
    <%= render feedback %>
  <% end %>
</div>
```

**What happens:** User A creates feedback → server broadcasts `<turbo-stream ac
Files: 1
Size: 20.3 KB
Complexity: 23/100
Category: General

Related in General