Refresh One Rails Section Without a Full Reload Using Turbo Frames
Use Turbo Frames to refresh one section of a Rails page—like comments or search results—without reloading the whole document.
01 Jul 2026, 01:59 UTC

The problem: a full page reload for one small change
You open a Rails page, submit a comment form, and the entire document disappears and redraws. Your scroll position is gone, the navigation re-fetches, and the browser spends time parsing HTML you already had. When only one corner of the page should change, reloading everything is wasted work.
The useful takeaway: Turbo Frames let Rails replace a single marked section of a page while leaving the rest of the document untouched. You do not need custom Ajax code, but the server must return HTML wrapped in a <turbo-frame> element whose ID matches the target on the current page.
How a Turbo Frame boundaries a request
A <turbo-frame id=\"comments\"> tag tells Turbo to treat that element as a navigation boundary. When a user clicks a link or submits a form inside the frame, Turbo intercepts the action and issues a fetch request instead of a full browser navigation.
Turbo then inspects the HTML response for a <turbo-frame id=\"comments\"> with the same ID. If it finds one, it replaces the existing frame's children with the new ones. If the IDs do not match, or the response contains no matching frame, Turbo falls back to a full page navigation. That silent fallback is the most common reason the feature appears broken.
This pattern assumes Turbo 7.x, as shipped with recent Rails versions. Confirm your installed Turbo version before relying on frame-specific behavior.
Worked example: comment form on a post page
Imagine a blog post show page where comments live inside their own frame. The form and the comment list are wrapped together so a submission refreshes only that block.
<%= turbo_frame_tag \"post_comments\" do %>
<h3>Comments</h3>
<% @post.comments.each do |comment| %>
<div class=\"comment\"><%= comment.body %></div>
<% end %>
<%= form_with model: [ @post, @post.comments.build ], local: true do |form| %>
<%= form.text_area :body %>
<%= form.submit \"Add comment\" %>
<% end %>
<% end %>
Because the form is inside the frame, Turbo submits it to the same URL and expects the response to contain a <turbo-frame id=\"post_comments\"> element.
What the controller action returns
The controller should render the frame partial again, not a full layout. This is illustrative; adapt the partial names to your application.
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.build(comment_params)
if @comment.save
render partial: \"comments/frame\", locals: { post: @post }
else
render partial: \"comments/frame\", locals: { post: @post }, status: :unprocessable_entity
end
end
end
The comments/frame partial repeats the same turbo_frame_tag \"post_comments\" wrapper with the updated comment list and a fresh form.
Check it in the browser
- Run
bin/rails serverfrom the Rails application root in a terminal. Normal user permissions are sufficient; the process binds tolocalhost:3000by default. - Open the post page in a browser and open Developer Tools.
- Click the Network tab, then submit the comment form.
- Expect a single
fetchrequest rather than a full document navigation. Inspect the response HTML and confirm it contains<turbo-frame id=\"post_comments\">. - Switch to the Elements tab. The outer
<turbo-frame>tag should remain in place while only its inner comment nodes change.
Risk: if the controller renders a full application layout or a frame with a mismatched ID, Turbo performs a full page navigation with no console error. Check the Network tab first when the page appears to reload entirely.
Trade-offs and limitations
- Nested frames are fragile. Frame IDs must be unique across the page. Nesting frames can make it unclear which frame handles a click, especially when links target ancestor frames.
- The URL does not change. A Turbo Frame replacement updates the DOM but leaves the browser address bar alone. If users need a shareable URL for the updated state, combine frames with Turbo Drive or Turbo Stream, or manage history explicitly.
- Server and client IDs must stay aligned. Renaming a frame in the view without updating the controller response causes silent full-page fallbacks.
Start with one frame
Pick one isolated widget—a comment list, a pagination block, or an inline search filter—and wrap it in turbo_frame_tag. Make sure the controller action that updates it returns HTML inside a frame with the identical ID. Verify with the Network and Elements tabs before expanding the pattern to more complex UI.
Turbo Frames remove full-page reloads for common partial updates, but they trade that simplicity for strict ID matching and careful frame boundaries.
Editorial note: this draft describes established Turbo Frame behavior but has not been verified against a live Rails and Turbo installation. Confirm the code paths and version assumptions before production use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.