Character counter for textareas with Stimulus

A character counter is small, but it removes uncertainty for users (especially when there’s a limit). I implement it with Stimulus so it stays reusable: attach to a wrapper, declare input + output targets, and compute remaining characters based on an

Inline form validation feedback via Turbo Streams

When forms live inside a Turbo Frame (modal or inline edit), validation needs to feel immediate without a full-page refresh. I render the form inside a frame with an ID like post_form. On submit, create/update returns status: :unprocessable_entity and

Infinite scrolling list using lazy Turbo Frames

Turbo Frames can implement infinite scrolling without a JS router. I render the first page normally and append a “next page” frame at the bottom with loading: :lazy. When the user scrolls and the frame enters view, Turbo fetches the next page automati

Debounced live search with Stimulus + Turbo Streams

For search-as-you-type, I keep the server in charge and use a small Stimulus controller to debounce form submission. The controller listens to input events, waits ~250ms, then triggers a normal Turbo form submit. The server responds with index.turbo_s

Optimistic toggle button with Stimulus “revert on failure”

I like optimistic UI for tiny interactions (like “star” or “follow”) because it makes the interface feel instant. The tradeoff is handling failure cleanly. I implement this with Stimulus: flip CSS + text immediately, then submit a Turbo request in 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

Inline edit a table row with Turbo Frames

For admin-ish UIs, inline editing is the best balance of speed and clarity. I wrap each row in a turbo_frame_tag keyed by dom_id(record) and render either a read-only row partial or an edit form partial inside the same frame. Clicking “Edit” targets t

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

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

Vite env handling: explicit prefixes only

Leaking secrets into the browser bundle is an easy mistake. Vite only exposes env vars with the VITE_ prefix, and I keep that rule strict. I also define a small typed wrapper so components don’t read import.meta.env directly everywhere. The wrapper gi

Prettier config for consistent formatting

Formatting consistency matters, but humans shouldn’t be the ones enforcing it in code review. Prettier makes diffs smaller and reviews more focused on logic. I keep the config minimal and aligned with team expectations (singleQuote, trailing commas, ~

ESLint config that avoids bikeshedding

I want linting to catch bugs, not fuel style debates. I use ESLint for correctness rules (unused vars, no-floating-promises, React hooks rules) and let Prettier handle formatting. I keep overrides minimal and justified. I also treat lint as part of CI