turbo

Reorder a list server-side and reflect instantly with Turbo Streams

Drag-and-drop reorder can be fancy, but the core is: user triggers a reorder action, server persists positions, and the UI updates. For simpler UIs, I skip drag-and-drop and use up/down buttons. Each click POSTs to a move_up action, updates position,

Autofocus first input when a Turbo modal opens (Stimulus)

A modal that opens without focusing an input is a tiny annoyance that adds up. In Hotwire apps, modals often swap in via Turbo Frames, which means the DOM is injected after navigation. Stimulus is ideal here: attach an autofocus controller to the moda

Turbo Stream flash messages without custom JS

Instead of sprinkling custom JS for notifications, I treat flash as UI state and render it via Turbo Streams. When a create/update succeeds, the controller responds to format.turbo_stream and the template uses turbo_stream.replace to swap the flash co

Model broadcasts: prepend on create, replace on update

When updates can happen from multiple places, model-level broadcasts keep the UI consistent across tabs without sprinkling stream logic in controllers. Use after-commit hooks so broadcasts only occur once the write is durable.

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)

Turbo-Location header: redirect a frame submission to a new URL

When a form submits inside a Turbo Frame, a normal redirect can sometimes feel odd (especially if the redirect response doesn’t include the matching frame). A clean approach is to set the Turbo-Location header for Turbo requests. Turbo interprets it a

Turbo Streams: optimistic UI for likes with disable-on-submit

A small UX win: disable the like button immediately and re-enable on failure. Turbo gives you events; Stimulus coordinates button state and the server still returns the canonical count.