Turbo Streams: inline “saved” indicator that updates on autosave

Users trust autosave when they can see it. I render a small #save_state region (“Saved just now”, “Saving…”, “Offline”) and update it via Turbo Streams. The autosave endpoint returns a stream that replaces the indicator, and optionally updates a hidde

Filter UI that syncs query params via Stimulus (no front-end router)

Filters are better when the URL reflects state. I use a small Stimulus controller that updates the query string as filters change, then triggers a Turbo visit (often with data-turbo-action='replace'). This gives shareable URLs and correct back-button

Keyboard shortcut “command palette” modal (Hotwire-first)

A command palette feels like a SPA feature, but you can do it Hotwire-first: place a turbo_frame_tag 'modal' in the layout and load the palette HTML into it. A small Stimulus controller listens for meta+k and navigates the modal frame to /palette. The

Server-rendered skeleton UI for slow frames

For slow endpoints, I like a skeleton placeholder rather than a blank area. Turbo Frames make this easy: render a “loading skeleton” inside the frame tag as the initial content, then set src so Turbo fetches the real content. When the response arrives

Use data-turbo-action to control history (advance vs replace)

Turbo Drive records visits in browser history. For some interactions (like typing through search results), you don’t want every click to create a new history entry. Turbo supports data-turbo-action='replace' to replace the current history entry instea

Turbo Frames: inline “details drawer” without a SPA router

A common UI is a list on the left and a details panel (drawer) on the right. With Turbo Frames, each list item link can target a details frame. Clicking an item swaps the drawer content while leaving the list intact. The server still renders HTML, so

Turbo Streams: swap a button state and counter in one response

A “follow” button usually needs two updates: the button label/state and the follower count. Turbo Streams make this trivial because one server response can carry multiple DOM operations. I render both UI pieces as partials with stable targets (dom_id(

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

Undo delete with a Turbo Stream “restore” action

Undo is a great UX improvement for destructive actions. My approach is: on destroy, soft-delete the record (or keep enough data to restore), remove it from the list via turbo_stream.remove, and append a toast with an “Undo” link. Clicking “Undo” hits

Use dom_id everywhere to keep Turbo replacements deterministic

Turbo Stream updates are easiest when every meaningful element has a stable id. Rails’ dom_id helper makes this painless: dom_id(@post) yields post_123, and dom_id(@post, :header) yields header_post_123. I use this pattern throughout Hotwire UIs so I

Inline create form that prepends into a list with Turbo Streams

My favorite Hotwire demo is the classic “inline create” on an index page. The form sits at the top of the page. When submitted, the create action returns turbo streams that (1) prepend the new item into the list and (2) replace the form with a fresh,

Scroll into view after append using a custom Turbo Stream action

When appending new content (like a new message), I sometimes want to scroll it into view automatically. Rather than adding JS in the controller, I use a custom turbo stream action scroll_into_view that finds the target element and calls scrollIntoView