# Hotwire

> Hotwire, Turbo, and Stimulus patterns for Rails. Use when implementing JavaScript interactions, Turbo Frames/Streams, or Stimulus controllers. Triggers on "stimulus controller", "turbo frame", "turbo stream", "hotwire", "rails javascript".

- Skill: `bibendi/hotwire` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bibendi/hotwire`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bibendi/hotwire/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bibendi (https://skillmd.com/u/bibendi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bibendi/hotwire

---


# Hotwire, Turbo & Stimulus for Rails

Expert patterns for JavaScript and Hotwire integration with Ruby on Rails.

## Core Principles

1. Use latest versions based on Gemfile
2. Follow Rails conventions and best practices
3. Use Context7 MCP or hotwire.dev for documentation
4. Test JavaScript with Vitest (./test/controllers)
5. Review existing Stimulus controllers before creating new ones

## Stimulus Controllers

### Guidelines
- Keep controllers simple and focused
- Make controllers generic when possible, specific only when needed
- **Never** have controllers communicate with each other
- Integrate into ERB templates using Rails conventions

### Structure
```javascript
// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["content"]
  static values = { open: Boolean }

  toggle() {
    this.openValue = !this.openValue
  }

  openValueChanged() {
    this.contentTarget.classList.toggle("hidden", !this.openValue)
  }
}
```

### ERB Integration
```erb
<div data-controller="toggle" data-toggle-open-value="false">
  <button data-action="toggle#toggle">Toggle</button>
  <div data-toggle-target="content" class="hidden">
    Content here
  </div>
</div>
```

## Turbo Frames

Use for partial page updates without full refreshes.

```erb
<%= turbo_frame_tag "user_profile" do %>
  <%= render @user %>
<% end %>

<!-- Link that updates only the frame -->
<%= link_to "Edit", edit_user_path(@user), data: { turbo_frame: "user_profile" } %>
```

## Turbo Streams

Use for real-time updates from server.

```ruby
# Controller
respond_to do |format|
  format.turbo_stream
  format.html { redirect_to @post }
end
```

```erb
<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "post_count", Post.count %>
```

## AJAX Requests

Use `request.js` for AJAX when needed:

```javascript
import { get, post } from "@rails/request.js"

async function loadData() {
  const response = await get("/api/data", { responseKind: "json" })
  if (response.ok) {
    const data = await response.json
    // handle data
  }
}
```

## Import Maps

Include JavaScript libraries via import maps. Only add libraries when absolutely necessary.

```ruby
# config/importmap.rb
pin "lodash", to: "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js"
```

If import maps aren't used, follow whatever asset pipeline the application uses.

