erb ruby javascript 61 lines · 4 tabs

Scoped navigation inside a sidebar with Turbo Frames

Shared by codesnips Jan 2026
4 tabs
<div class="layout" data-controller="sidebar">
  <aside class="sidebar">
    <%= render "shared/sidebar", active: params[:controller] %>
  </aside>

  <main class="content-area">
    <%= turbo_frame_tag "content",
          data: { action: "turbo:frame-load->sidebar#highlight" } do %>
      <h1>Welcome back, <%= current_user.name %></h1>
      <p>Select a report from the sidebar to get started.</p>
    <% end %>
  </main>
</div>
4 files · erb, ruby, javascript Explain with highlit

This snippet shows how a sidebar-driven layout keeps navigation snappy by only swapping the main content region, not the whole page, using Turbo Frames. The core idea is that a Turbo Frame acts as an independently-updatable region of the DOM: any link or form inside it whose response contains a matching <turbo-frame> will have just that region replaced, leaving the surrounding chrome untouched. The sidebar links themselves stay outside the content frame so they persist across navigations and never flicker.

In dashboard/index.html.erb, the page is split into a static <aside> and a <turbo-frame id="content">. The sidebar links each carry data: { turbo_frame: "content" }, which tells Turbo to target the content frame even though the link lives outside it. This is the crucial detail: without that attribute a link inside the sidebar would try to replace a frame wrapping the sidebar, or drive a full-page visit. A small Stimulus controller reference (data-controller="sidebar") handles active-state highlighting.

The _sidebar.html.erb partial renders the actual navigation entries via a nav_link helper. Each entry compares the current controller/action to decide its aria-current and active class, and every link explicitly declares turbo_frame: "content" so the behavior is consistent regardless of where the partial is included.

On the server side, ReportsController responds to those frame requests. The key trick is turbo_frame_request?, which lets the action render only the inner frame partial when Turbo asks for a frame, while still serving a full standalone page on a hard refresh or direct URL visit. This preserves deep-linkability and progressive enhancement: the app degrades gracefully to normal navigation if JavaScript is disabled.

The trade-off is that shared state like the active sidebar link must be reconciled after each swap, since the sidebar DOM is never re-rendered by the frame response. Turbo's turbo:frame-load event, wired through Stimulus, keeps highlighting in sync. This pattern is ideal for admin panels and dashboards where the shell is stable and only the working area changes, avoiding heavyweight SPA frameworks while keeping URLs, back-button behavior, and bookmarks fully functional.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Scoped navigation inside a sidebar with Turbo Frames — share card
Link copied