Choosing Turbo Frames for Incremental UI Updates in Rails
A decision guide for using Turbo Frames in Rails to replace page sections without custom JavaScript. Compare Frames vs Streams vs AJAX and see a concrete implementation.
28 Dec 2025, 09:08 UTC

The decision: partial page updates without custom JavaScript
When building a Rails application, you often encounter a requirement to update a specific part of a page—such as a comment section, a filterable product list, or a shopping cart summary—without triggering a full browser reload. The goal is to maintain a snappy user experience while keeping the rendering logic on the server.
The core decision is whether to use Turbo Frames, Turbo Streams, or traditional AJAX. The useful takeaway is: use a Turbo Frame when the update is self-contained within a single DOM container and the server can return that container's markup. If you need to update multiple unrelated areas of the page simultaneously, Turbo Frames are insufficient, and Turbo Streams should be used instead.
Constraints and trade-offs
Several technical constraints shape this decision:
- JavaScript Overhead: Turbo Frames require almost no client-side code; they rely on matching HTML IDs. In contrast, classic AJAX requires Stimulus controllers or vanilla JavaScript to handle fetch requests and manual DOM patching.
- Payload Size: Turbo Frames return a full HTML fragment wrapped in a frame tag. While this simplifies the server logic, it can be heavier than a JSON payload for very large data sets. Performance gains are most evident on low-latency networks where reducing round-trip complexity is more valuable than minimizing every byte.
- ID Scoping: Frames are identified by their
idattribute. The server response must contain a<turbo-frame>with a matching ID, or the browser will ignore the update. Nested frames must have unique IDs to prevent the browser from incorrectly attributing an update to an outer container.
Comparison of update strategies
| Approach | Best Use Case | Server Response | Client Effort | Update Scope |
|---|---|---|---|---|
| Turbo Frames | Self-contained region updates, inline forms | HTML fragment in <turbo-frame id="..."> |
None (HTML only) | Single container |
| Turbo Streams | Multi-region updates, real-time broadcasts | HTML stream with <turbo-stream action="..."> |
Minimal (ID targets) | Multiple elements |
| Classic AJAX | Complex client logic, JSON-heavy APIs | JSON or HTML | High (Fetch + DOM) | Arbitrary |
Concrete Implementation: Inline Commenting
Assume a Rails 7+ environment. To implement a comment form that updates only the comment list without reloading the post page, follow this pattern.
1. The View Wrapper
Wrap the target area using the turbo_frame_tag helper. This creates the <turbo-frame> element that Turbo monitors for updates.
<!-- app/views/posts/show.html.erb -->
<turbo-frame id="comments_<%= @post.id %>">
<%= render "comments/list", post: @post %>
<%= form_with model: [@post, Comment.new] do |f| %>
<%= f.text_area :body %>
<%= f.submit "Post Comment" %>
<%= end %>
</turbo-frame>
2. The Server Response
The controller handles the submission. To ensure only the frame is updated, the response must include the matching frame ID. If you redirect to the post page, Turbo will automatically extract the matching frame from the full page response and swap it.
# app/controllers/comments_controller.rb
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.build(comment_params)
if @comment.save
# Redirecting to the post page; Turbo extracts the frame with id "comments_#{@post.id}"
redirect_to post_path(@post)
else
render :new, status: :unprocessable_entity
end
end
Verification and Limitations
To verify the implementation, perform the following checks:
- DOM Inspection: Submit the form and ensure the rest of the page (e.g., the navigation bar or post title) does not flicker or reload.
- Network Analysis: Open the browser Network tab. You should see a request to the server, but the browser should not perform a full document reload.
- Log Check: Verify the server logs show the request and the subsequent render of the partial or page containing the target frame.
Limitations: If the server returns markup outside the frame, the browser may ignore it, which can lead to layout shifts if you expected other parts of the page to change. Additionally, if you need to update a "Total Comments" counter in the page header while updating the list in the body, a Turbo Frame cannot do this alone; you must transition to Turbo Streams.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.