hotwire-patterns

Hotwire Patterns for Rails 8

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hotwire-patterns" with this command: npx skills add thibautbaissac/rails_ai_agents/thibautbaissac-rails-ai-agents-hotwire-patterns

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)

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

<%# app/views/posts/create.turbo_stream.erb %> <%= turbo_stream.prepend "posts", @post %> <%= turbo_stream.update "flash", partial: "shared/flash" %>

Stimulus Controller

// 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") } }

<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 for frame patterns

  • See turbo-streams.md for stream patterns

  • See stimulus.md for controller patterns

Testing Hotwire

System Specs

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

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

<%# _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

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

<%= turbo_frame_tag "comments", src: post_comments_path(@post), loading: :lazy do %> <p>Loading comments...</p> <% end %>

Debugging Tips

  • Frame not updating? Check frame IDs match exactly

  • Stream not working? Verify Accept header includes turbo-stream

  • Stimulus not firing? Check controller name matches file name

  • Events not working? Use data-action="event->controller#method"

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

i18n-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

rails-controller

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

solid-queue-setup

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

viewcomponent-patterns

No summary provided by upstream source.

Repository SourceNeeds Review