Using Turbo Frames for Inline Editing in Rails Without Writing JavaScript
Learn how to implement inline comment editing in Rails using Turbo Frames, avoiding custom JavaScript while preserving graceful degradation.
04 Mar 2026, 17:34 UTC

Problem: Editing a Comment Inline Without Custom JavaScript
You have a list of comments on a Rails page and you want users to edit a comment directly in place, seeing the updated text instantly without a full‑page reload. Writing a small Stimulus controller or custom fetch logic feels heavy for such a simple interaction, and you’d rather keep the view logic in ERB.
Thesis: Turbo Frames let you swap only the marked HTML fragment when a link or form is submitted, giving you the same inline‑edit experience with zero custom JavaScript.
How Turbo Frames Work (Briefly)
Turbo intercepts clicks on links and submissions of forms that carry a data-turbo-frame attribute. The request is sent normally, but Turbo extracts from the response the element whose id matches the frame’s id and replaces the original frame’s DOM node with that fragment. The rest of the page stays untouched, preserving scroll position, focus, and any surrounding state.
Worked Example: Inline Comment Edit
- Generate a simple controller (run in your Rails project root):
rails generate controller Comments edit update - Add routes in
config/routes.rb:resources :comments, only: [:edit, :update] - Create the edit form partial (
app/views/comments/_form.html.erb):<!-- This partial will be rendered inside a Turbo Frame --> <%= form_with model: comment, url: comment_path(comment), method: :patch, data: { turbo_frame: dom_id(comment) } do |f| %> <div class="field"> <%= f.label :body %> <%= f.text_area :body, rows: 3 %> </div> <div class="actions"> <%= f.submit "Save", data: { disable_with: "Saving..." } %> <%= link_to "Cancel", comment, data: { turbo_frame: dom_id(comment) } %> </div> <% end %> - Show the comment list with frames (
app/views/comments/index.html.erb):<h1>Comments</h1> <%= @comments.each do |comment| %> <!-- Each comment gets its own Turbo Frame --> <turbo-frame id="<%= dom_id(comment) %>"> <%= comment.body %> <br> <%= link_to "Edit", edit_comment_path(comment), data: { turbo_frame: dom_id(comment) } %> </turbo-frame> <% end %> - Implement the controller actions (
app/controllers/comments_controller.rb):def edit @comment = Comment.find(params[:id]) respond_to do |format| format.html { render partial: "form", locals: { comment: @comment } } end end def update @comment = Comment.find(params[:id]) if @comment.update(comment_params) # After a successful update we render the comment’s show fragment respond_to do |format| format.html { render partial: "comment", locals: { comment: @comment } } end else respond_to do |format| format.html { render partial: "form", locals: { comment: @comment }, status: :unprocessable_entity } end end end private def comment_params params.require(:comment).permit(:body) end - Create a tiny partial for the comment display (
app/views/comments/_comment.html.erb):<%= comment.body %> <br> <%= link_to "Edit", edit_comment_path(comment), data: { turbo_frame: dom_id(comment) } %>
What Happens When You Click “Edit”
- The link carries
data-turbo-frame="comment_42"(the dom_id of the comment). - Turbo sends a GET to
/comments/42/edit. - The
editaction renders the_formpartial. - Turbo extracts the
<turbo-frame id="comment_42">from the response and replaces the original frame’s DOM node with the form. - Submitting the form (a PATCH) follows the same flow, swapping back the updated comment text.
Trade‑offs and Limitations
- Unique IDs are required. If two frames on the same page share an id, Turbo may swap the wrong element, leading to lost state or duplicate content.
- Server must return HTML. JSON or plain‑text responses cause Turbo to treat the update as a failure, leaving the frame unchanged (no error is shown in the UI).
- Nested frames need distinct ids; a frame inside another frame must not reuse the parent’s id.
- Graceful degradation. With JavaScript disabled, the link behaves as a normal full‑page navigation, so the edit still works albeit with a page reload.
Actionable Closing: Verify It Works
- Start the server:
rails server(run in your terminal, no special permissions needed beyond access to the project). - Open
http://localhost:3000/commentsin a browser. - Click “Edit” on a comment – you should see the form replace the comment text without the URL changing.
- Open DevTools → Network tab, verify the request is a standard GET/POST and the response contains full HTML.
- In the Elements tab, confirm only the
<turbo-frame id="comment_42">node changed; surrounding elements stay the same. - Disable JavaScript (via DevTools → Settings → Disable JavaScript) and repeat the click – the browser should navigate to
/comments/42/editas a full page, confirming graceful degradation.
By following these steps you get an inline edit feature that relies solely on Turbo Frames, keeping your JavaScript footprint minimal while still delivering a smooth, single‑page‑app‑like experience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.