home / skills / thibautbaissac / rails_ai_agents / hotwire-patterns
/skills/hotwire-patterns
This skill helps you implement and orchestrate Hotwire patterns (Turbo Frames, Streams, Stimulus) for real-time, interactive Rails UIs.
npx playbooks add skill thibautbaissac/rails_ai_agents --skill hotwire-patternsReview the files below or copy the command above to add this skill to your agents.
---
name: hotwire-patterns
description: Implements Hotwire patterns with Turbo Frames, Turbo Streams, and Stimulus controllers. Use when building interactive UIs, real-time updates, form handling, partial page updates, or when user mentions Turbo, Stimulus, or Hotwire.
allowed-tools: Read, Write, Edit, Bash
---
# Hotwire Patterns for Rails 8
## Overview
Hotwire = HTML Over The Wire - Build modern web apps without writing much JavaScript.
| Component | Purpose | Use Case |
|-----------|---------|----------|
| **Turbo Drive** | SPA-like navigation | Automatic, no code needed |
| **Turbo Frames** | Partial page updates | Inline editing, tabbed content |
| **Turbo Streams** | Real-time DOM updates | Live updates, flash messages |
| **Stimulus** | JavaScript sprinkles | Toggles, forms, interactions |
## Quick Start
### Turbo Frames (Scoped Navigation)
```erb
<%# app/views/posts/index.html.erb %>
<%= turbo_frame_tag "posts" do %>
<%= render @posts %>
<%= link_to "Load More", posts_path(page: 2) %>
<% end %>
<%# Clicking "Load More" only updates content inside this frame %>
```
### Turbo Streams (Real-time Updates)
```erb
<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "flash", partial: "shared/flash" %>
```
### Stimulus Controller
```javascript
// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["content"]
toggle() {
this.contentTarget.classList.toggle("hidden")
}
}
```
```erb
<div data-controller="toggle">
<button data-action="toggle#toggle">Toggle</button>
<div data-toggle-target="content">Hidden content</div>
</div>
```
## Workflow Checklist
```
Hotwire Implementation:
- [ ] Identify update scope (full page vs partial)
- [ ] Choose pattern (Frame vs Stream vs Stimulus)
- [ ] Implement server response
- [ ] Add client-side markup
- [ ] Test with and without JavaScript
- [ ] Write system spec
```
## When to Use Each Pattern
| Scenario | Pattern | Why |
|----------|---------|-----|
| Inline edit | Turbo Frame | Scoped replacement |
| Form submission | Turbo Stream | Multiple updates |
| Real-time feed | Turbo Stream + ActionCable | Push updates |
| Toggle visibility | Stimulus | No server needed |
| Form validation | Stimulus | Client-side feedback |
| Infinite scroll | Turbo Frame + lazy loading | Paginated content |
| Modal dialogs | Turbo Frame | Load on demand |
| Flash messages | Turbo Stream | Append/update |
## References
- See [turbo-frames.md](reference/turbo-frames.md) for frame patterns
- See [turbo-streams.md](reference/turbo-streams.md) for stream patterns
- See [stimulus.md](reference/stimulus.md) for controller patterns
## Testing Hotwire
### System Specs
```ruby
# spec/system/posts_spec.rb
require 'rails_helper'
RSpec.describe "Posts", type: :system do
before { driven_by(:selenium_chrome_headless) }
it "updates post inline with Turbo Frame" do
post = create(:post, title: "Original")
visit posts_path
within("#post_#{post.id}") do
click_link "Edit"
fill_in "Title", with: "Updated"
click_button "Save"
end
expect(page).to have_content("Updated")
expect(page).not_to have_content("Original")
end
it "adds comment with Turbo Stream" do
post = create(:post)
visit post_path(post)
fill_in "Comment", with: "Great post!"
click_button "Add Comment"
within("#comments") do
expect(page).to have_content("Great post!")
end
end
end
```
### Request Specs for Turbo Stream
```ruby
# spec/requests/posts_spec.rb
RSpec.describe "Posts", type: :request do
describe "POST /posts" do
let(:valid_params) { { post: { title: "Test" } } }
it "returns turbo stream response" do
post posts_path, params: valid_params,
headers: { "Accept" => "text/vnd.turbo-stream.html" }
expect(response.media_type).to eq("text/vnd.turbo-stream.html")
expect(response.body).to include("turbo-stream")
end
end
end
```
## Common Patterns
### Inline Editing with Frame
```erb
<%# _post.html.erb %>
<%= turbo_frame_tag dom_id(post) do %>
<article>
<h2><%= post.title %></h2>
<%= link_to "Edit", edit_post_path(post) %>
</article>
<% end %>
<%# edit.html.erb %>
<%= turbo_frame_tag dom_id(@post) do %>
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.submit "Save" %>
<%= link_to "Cancel", @post %>
<% end %>
<% end %>
```
### Flash Messages with Stream
```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
after_action :flash_to_turbo_stream, if: -> { request.format.turbo_stream? }
private
def flash_to_turbo_stream
flash.each do |type, message|
flash.now[type] = message
end
end
end
```
### Lazy Loading Frame
```erb
<%= turbo_frame_tag "comments", src: post_comments_path(@post), loading: :lazy do %>
<p>Loading comments...</p>
<% end %>
```
## Debugging Tips
1. **Frame not updating?** Check frame IDs match exactly
2. **Stream not working?** Verify `Accept` header includes turbo-stream
3. **Stimulus not firing?** Check controller name matches file name
4. **Events not working?** Use `data-action="event->controller#method"`
This skill implements Hotwire patterns for Rails 8.1 using Turbo Frames, Turbo Streams, and Stimulus controllers. It provides practical patterns, test examples, and a workflow checklist to build interactive UIs with minimal custom JavaScript. Use it to structure partial updates, real-time DOM changes, and small client-side behaviors.
The skill inspects UI intentions and suggests the right Hotwire pattern: Turbo Frames for scoped partial updates and navigation, Turbo Streams for server-pushed DOM changes, and Stimulus for lightweight client interactions. It supplies sample markup, controller snippets, and test examples (system and request specs) so you can implement and verify each pattern. It also includes debugging tips and a checklist to ensure reliable behavior with and without JavaScript.
What response format should my controller return for Turbo Streams?
Return 'text/vnd.turbo-stream.html' and render a .turbo_stream.erb template containing turbo_stream actions.
Why does my Turbo Frame not update?
Verify the turbo_frame_tag ID matches the returned frame ID exactly and that the response contains only the frame markup.