---
name: hotwire-turbo-stimulus
description: This skill should be used when the user asks about Hotwire, Turbo Drive, Turbo Frames, Turbo Streams, Stimulus controllers, frontend interactivity, server-rendered HTML updates, websocket updates, progressive enhancement, data attributes, importing JavaScript modules, or building interactive UIs without React/Vue. Also use when discussing the "HTML over the wire" approach or NO BUILD philosophy. Examples:
Context: User wants to update part of a page without full reload
user: "How can I update just the product list without refreshing the entire page?"
assistant: "I'll show you Turbo Frames for partial page updates."
This relates to Turbo Frames for scoped page updates.
Context: User needs real-time updates
user: "How do I push updates to users when new comments are added?"
assistant: "Turbo Streams over WebSockets is perfect for this. Let me explain."
This involves Turbo Streams with Action Cable for real-time broadcasts.
Context: User wants JavaScript interactivity
user: "I need a dropdown menu that opens on click"
assistant: "This is a perfect use case for a Stimulus controller. Let me show you."
This relates to Stimulus for lightweight JavaScript interactions.
---
# Hotwire/Turbo/Stimulus: Modern Rails Frontend
## Overview
Hotwire (HTML Over The Wire) is Rails' answer to frontend complexity. Instead of shipping JSON to a heavy JavaScript framework, Hotwire delivers HTML directly from the server.
**Hotwire consists of:**
- **Turbo Drive**: Fast navigation without full page reloads
- **Turbo Frames**: Update specific page sections
- **Turbo Streams**: Real-time HTML updates via WebSockets or HTTP
- **Stimulus**: Lightweight JavaScript controllers for sprinkles of interactivity
Together, they provide rich, reactive UIs with minimal JavaScript and no build step.
## Philosophy
Hotwire reflects Rails 8's core principles:
- **NO BUILD**: No webpack, no complex toolchains
- **Server-rendered**: HTML comes from Rails, not JavaScript
- **Progressive enhancement**: Start with HTML, add JavaScript as needed
- **Minimal JavaScript**: Write only what browsers can't do
- **Integrated**: Works seamlessly with Rails conventions
Most applications need less JavaScript than you think. Hotwire proves it.
## Turbo Drive
### What It Does
Turbo Drive intercepts link clicks and form submissions, replacing full page loads with AJAX requests that update the page content.
**Without Turbo Drive:**
```
Click link → Browser requests page → Full page reload → JavaScript re-initializes
```
**With Turbo Drive:**
```
Click link → AJAX request → Replace
→ Fast transition
```
Benefits:
- Instant navigation
- Preserved scroll position
- CSS/JS stay loaded
- Smooth transitions
### How It Works
Automatically enabled when you include Turbo:
```javascript
// app/javascript/application.js
import "@hotwired/turbo-rails"
```
Now all links and forms use Turbo Drive automatically:
```erb
<%= link_to "Products", products_path %>
<%= form_with model: @product do |f| %>
<% end %>
```
### Disabling Turbo Drive
For specific links/forms:
```erb
<%= link_to "External", "https://example.com", data: { turbo: false } %>
<%= form_with model: @product, data: { turbo: false } do |f| %>
<% end %>
```
### Turbo Drive Progress Bar
Built-in progress indicator for navigation:
```css
/* Customize progress bar */
.turbo-progress-bar {
height: 5px;
background-color: #0076ff;
}
```
## Turbo Frames
### What They Do
Turbo Frames let you update specific page sections without affecting the rest of the page.
**Traditional approach:**
```
Update product → Full page reload → Entire page re-renders
```
**With Turbo Frames:**
```
Update product → Only product frame updates → Rest of page untouched
```
### Basic Usage
```erb
Products
<%= turbo_frame_tag "new_product" do %>
<%= link_to "New Product", new_product_path %>
<% end %>
<%= render @products %>
<%= turbo_frame_tag "new_product" do %>
New Product
<%= form_with model: @product do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
<% end %>
```
When clicking "New Product", only the `new_product` frame updates—the rest of the page stays.
### dom_id Helper
Rails provides `dom_id` for consistent frame IDs:
```erb
<%= turbo_frame_tag dom_id(@product) do %>
<%= render @product %>
<% end %>
```
### Frame Navigation
```erb
<%= turbo_frame_tag "products" do %>
<% @products.each do |product| %>
<%= link_to product.name, product_path(product) %>
<% end %>
<% end %>
```
### Breaking Out of Frames
Navigate the full page from within a frame:
```erb
<%= link_to "View All", products_path, data: { turbo_frame: "_top" } %>
```
### Lazy Loading Frames
Load content on demand:
```erb
<%= turbo_frame_tag "lazy_content", src: lazy_products_path do %>
Loading...
<% end %>
```
See `references/turbo-frames.md` for advanced patterns.
## Turbo Streams
### What They Do
Turbo Streams deliver targeted HTML updates after form submissions or via WebSockets.
**Seven Stream Actions:**
- **append**: Add to bottom of target
- **prepend**: Add to top of target
- **replace**: Replace entire target
- **update**: Replace target's content (keeps the target element)
- **remove**: Delete target
- **before**: Insert before target
- **after**: Insert after target
### After Form Submission
```ruby
# app/controllers/products_controller.rb
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.turbo_stream { render turbo_stream: turbo_stream.prepend("products", @product) }
format.html { redirect_to @product }
else
format.turbo_stream { render turbo_stream: turbo_stream.replace(dom_id(@product), partial: "form", locals: { product: @product }) }
format.html { render :new, status: :unprocessable_entity }
end
end
end
```
```erb
<%= render @products %>
<%= turbo_frame_tag "new_product" do %>
<%= render "form", product: @product %>
<% end %>
```
When form submits, new product prepends to #products list without page reload.
### Broadcast Updates (Real-Time)
Stream changes to all connected users:
```ruby
# app/models/product.rb
class Product < ApplicationRecord
after_create_commit -> { broadcast_prepend_to "products", target: "products" }
after_update_commit -> { broadcast_replace_to "products" }
after_destroy_commit -> { broadcast_remove_to "products" }
end
```
```erb
<%= turbo_stream_from "products" %>
<%= render @products %>
```
Now when any user creates/updates/deletes a product, all connected users see the change instantly.
### Multiple Stream Actions
```ruby
# app/views/products/create.turbo_stream.erb
<%= turbo_stream.prepend "products", @product %>
<%= turbo_stream.update "counter", Product.count %>
<%= turbo_stream.replace "flash", partial: "shared/flash" %>
```
See `references/turbo-streams.md` for broadcasting patterns.
## Stimulus
### What It Does
Stimulus is a modest JavaScript framework for adding behavior to HTML.
**Philosophy:**
- HTML is the source of truth
- Controllers connect to HTML via data attributes
- Small, focused controllers
- Progressive enhancement
### Basic Structure
```javascript
// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["menu"]
toggle() {
this.menuTarget.classList.toggle("hidden")
}
}
```
```erb
```
### Data Attributes
Stimulus uses three data attributes:
1. **data-controller**: Connects element to Stimulus controller
2. **data-{controller}-target**: Marks element as a target
3. **data-action**: Connects event to controller method
### Targets
Reference elements in controllers:
```javascript
export default class extends Controller {
static targets = ["input", "output", "button"]
connect() {
console.log(this.inputTarget) // First matching element
console.log(this.inputTargets) // All matching elements
console.log(this.hasInputTarget) // Boolean check
}
}
```
```erb