<div class="post">
<h1><%= @post.title %></h1>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<div class="post-content">
<%= simple_format @post.body %>
</div>
<% if can? :edit, @post %>
<%= link_to "Edit", edit_post_path(@post), class: "btn btn-secondary" %>
<% end %>
<% end %>
</div>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<%= form_with model: @post, class: "space-y-4" do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title, class: "form-input" %>
</div>
<div>
<%= f.label :body %>
<%= f.text_area :body, rows: 10, class: "form-input" %>
</div>
<div class="flex gap-2">
<%= f.submit "Save", class: "btn btn-primary" %>
<%= link_to "Cancel", post_path(@post), class: "btn btn-secondary" %>
</div>
<% end %>
<% end %>
Turbo Frames scope navigation to a specific section of the page, making inline editing feel instant without full page reloads. When a link or form inside a <turbo-frame> is submitted, only that frame's content updates. I use this pattern extensively for edit-in-place workflows where users click edit, modify content, and save—all without leaving the page. The data-turbo-frame attribute on links can target frames by ID, enabling cross-frame navigation. The key advantage is progressive enhancement: if JavaScript fails, forms still work with traditional full-page submissions. I also use lazy-loaded frames with src and loading='lazy' to defer expensive rendering until the frame scrolls into view.