Add/remove nested fields with Stimulus (no cocoon)

Nested fields are a common Rails pain point, and the old solution was heavy JS gems. With Stimulus, I keep it minimal: render a hidden <template> containing the fields with a placeholder index, then on “Add” clone it and swap the placeholder for

Live counter updates with Turbo Streams (likes, votes)

Counters (likes, votes, bookmarks) are classic UI glue. I keep the counter itself in a small partial with a stable id and update it via turbo streams on create/destroy. The controller can render a turbo_stream.replace of the counter plus (optionally)

Link that submits DELETE with Turbo (data-turbo-method)

In Hotwire apps, I still use Rails’ RESTful routes heavily. For destructive actions from a link, data-turbo-method='delete' is the cleanest approach: you keep semantic links where appropriate and let Turbo perform the method override. I usually pair i

A reusable respond_to block for turbo_stream + html

Once you’ve built a few Hotwire controllers, you notice the same respond_to shape repeated. I extract a small helper (a concern) that yields a respond_to block and centralizes the “default HTML redirect” pattern. This keeps controllers readable and re

Use 303 See Other after POST in Turbo flows

After a POST, Turbo behaves best when you redirect with 303 See Other (Rails symbol :see_other). This avoids the browser trying to re-submit the POST when the user refreshes, and it plays nicely with Turbo Drive’s navigation semantics. I use it especi

Render without layout for Turbo Frame requests

A classic Turbo foot-gun is returning a full layout inside a frame, which results in nested <html> and weird styling. The simplest fix is layout -> { turbo_frame_request? ? false : 'application' } at the controller level. I use this when I ha

Time-ago formatting with Stimulus (no heavy date libs)

For small UX touches like “3 minutes ago”, I don’t want to pull in a giant date library. A Stimulus controller can use Intl.RelativeTimeFormat plus a lightweight difference calculation. The server renders the ISO timestamp (via time_tag), and the cont

Importmap setup for Stimulus controllers (Rails 7 style)

If you’re using importmap, a clean Stimulus setup matters: it keeps controllers discoverable and avoids mystery load order bugs. I pin @hotwired/turbo-rails and @hotwired/stimulus, then use controllers index discovery to register everything. The payof

Active Storage direct upload progress with Stimulus

Direct uploads are great because they keep file traffic away from your Rails dynos, but the default UX is opaque. I attach a Stimulus controller that listens for Active Storage’s direct-upload:* events and updates a progress bar. This keeps the markup

Action Text comment form that works inside a Turbo Frame

Action Text is great, but you need to be deliberate about how you render it in a Turbo-powered UI. I keep the comment form inside a turbo_frame_tag so it can be replaced on validation errors (still 422). On success, I clear the form by replacing the f

Notifications dropdown that live-updates with Turbo Streams

For a notifications dropdown, I keep the list server-rendered and let Turbo Streams keep it fresh. The dropdown content sits in a turbo_frame_tag (so you can also navigate to a full notifications page), and the unread badge is a separate small target

Remove deleted items instantly with turbo_stream.remove

Destructive actions should feel immediate. For delete links inside a list, I return a Turbo Stream that does turbo_stream.remove dom_id(record). That removes the DOM node without re-rendering the rest of the list, which avoids the common “jump” effect