ux

Collapse/expand UI with Stimulus that survives Turbo swaps

Simple disclosure components are everywhere: FAQ, details panels, advanced filters. I keep them as progressive enhancement: the HTML is valid and readable, and Stimulus adds toggling behavior. The controller toggles a hidden class and updates aria-exp

Frontend: normalize and display server validation errors

Server-side validation is the source of truth, but raw error payloads are rarely UI-friendly. I normalize validation errors into a Record<field, message> shape so forms can render them consistently. The tricky detail is mapping server field path

Stimulus toast auto-dismiss for Turbo-appended notifications

When toasts are appended via Turbo Streams, they should self-dismiss without extra wiring. I attach a Stimulus controller to the toast element that schedules removal after a delay (and allows manual close). This means the server only needs to render 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

Frontend: skeleton loading instead of spinners

Spinners hide layout shifts and make an app feel slow even when it isn’t. Skeletons preserve layout and give users a sense of progress without jumping content around. I keep skeleton components simple and match the shape of the final UI. One practical

Turbo Streams: append server-side validation warnings

Sometimes you want to show non-blocking warnings (e.g., “link looks unreachable”) while still saving. Turbo streams can append warnings to a panel without rerendering the whole page.

Debounced search input (React)

Search boxes are a classic place to accidentally fire a request on every keystroke. I debounce the value that triggers the query (not the input rendering) so typing stays responsive, and I cancel/ignore stale requests so results don’t arrive out of or

Toast notifications delivered with Turbo Streams

Toasts are a great fit for Turbo Streams because they’re ephemeral UI that doesn’t need a dedicated page. I keep a #toasts container in the layout and turbo_stream.append a toast partial on success events. The toast itself is just HTML with a Stimulus

Frame-powered inline “quick view” that falls back to full page

A “quick view” is basically a show page rendered inside a frame. I implement it by adding a turbo_frame_tag 'quick_view' on the index page, and making item links target that frame. If Turbo is disabled or if the response doesn’t include the frame, the

Turbo Frame modal that renders server HTML

When I need a modal (new/edit/show), I avoid client-side templating by using a dedicated turbo_frame_tag as the modal container (often id='modal'). Links target that frame, so the response only replaces the modal content. Closing the modal is just swa

Keep navbar state across Turbo navigations with data-turbo-permanent

Some UI elements should survive navigation: a music player, a search input, or a navbar with an open dropdown. Turbo’s data-turbo-permanent lets you mark a DOM node that shouldn’t be replaced during visits. I use it carefully—permanent nodes can keep

Scoped navigation inside a sidebar with Turbo Frames

Sometimes you want only part of the screen to navigate—like a sidebar list updating the main content. Turbo Frames can do this cleanly: render the sidebar normally, and make its links target a turbo_frame_tag called main. Clicking a link swaps the mai