Sidebar
A composable, collapsible navigation panel wired to a Stimulus controller. Build app shells with a persistent left (or right) drawer that collapses off-canvas.
Installation
bin/rails g shadcnrb:component sidebar
Note
Because this docs page is itself rendered inside a sidebar layout, embedding a full
sidebar_wrapper in the preview
would create a nested layout conflict. Instead, the individual building blocks are shown
below. For a full working demo, see the
sidebar playground.
Usage — individual pieces
Child parts are reached via the proxy yielded by
sui.sidebar_wrapper do |s|. For
isolated demos outside a wrapper, sui.sidebar_proxy
returns the same proxy without rendering the shell.
menu_button mirrors Rails
link_to — pass a name + path and the active state is
auto-detected via current_page?.
<!-- default --> <%= s.menu_button "Dashboard", "#" %> <!-- active: true --> <%= s.menu_button "Active item", "#", active: true %> <!-- icon: :home --> <%= s.menu_button "Home", "#", icon: :home %>
For anything beyond a plain link — button_to with
method: :delete, Turbo options, nested icons — pass a
block. The wrapper renders a styled <div role="button">
and your Rails helper fills the row.
<!-- menu_button (block form with button_to) --> <div class="w-64 bg-sidebar rounded-md p-2"> <%= s.menu_button do %> <%= button_to "Sign out", "#", method: :delete %> <% end %> </div> <!-- group_label --> <div class="w-48 bg-sidebar rounded-md"> <%= s.group_label "Platform" %> </div> <!-- separator --> <div class="w-48 bg-sidebar rounded-md py-2"> <%= s.separator %> </div>
Sub-menu flyout
A collapsible group loses its menu_sub when the rail
collapses to icons. s.collapsible flyout: true keeps
the nested pages reachable: on the icon rail, hovering (or focusing)
the menu button opens the same list as a panel to its right, headed by
the button's label. Expanded, the group behaves as before. The click is
inert while the flyout is on, so the inline open state survives a
collapse and expand. Try it collapsed in the
playground
or the
Nav + Sidebar block.
s.collapsible open: true, flyout: true do |c| s.menu_item do s.menu_button "Docs", collapsible: true, icon: :folder do c.chevron end end c.content do s.menu_sub do s.menu_sub_item { s.menu_sub_button "Introduction", "#" } s.menu_sub_item { s.menu_sub_button "Installation", "#" } end end end
Full structure (code only)
Assemble these methods to build a complete sidebar shell. The
sidebar_wrapper owns the
Stimulus controller; s.trigger
and s.rail both dispatch
the toggle action.
sui.sidebar_wrapper do |s|
s.sidebar do
s.header do
tag.span "My App", class: "font-semibold text-sm px-2"
end
s.content do
s.group do
s.group_label "Platform"
s.group_content do
s.menu do
s.menu_item do
s.menu_button "Dashboard", root_path
end
s.menu_item do
s.menu_button "Settings", settings_path
end
end
end
end
s.separator
s.group do
s.group_label "Account"
s.group_content do
s.menu do
s.menu_item do
s.menu_button "Profile", profile_path
end
end
end
end
end
s.footer do
tag.span "Footer content"
end
s.rail
end
s.inset do
tag.header(class: "flex items-center gap-2 p-4") do
s.trigger
end
tag.div(class: "p-4") do
tag.p "Main content area"
end
end
end
Persisting on the server
The cookie is the default store. To keep the choice with the user's
account, pass the stored value to the helper and listen for the
change event — see
State persistence.
API
| method | signature | description |
|---|---|---|
sui.sidebar_wrapper | (state:, bounded:, **opts, &block) | Outermost div. Mounts the Stimulus sidebar controller and sets CSS custom properties for width. Renders state: (:expanded / :collapsed), else the sidebar_state cookie, so a collapsed sidebar never flashes expanded. Each desktop toggle dispatches shadcnrb--sidebar--component:change with { state }. Yields a proxy s. |
sui.sidebar_proxy | — | Returns a bare proxy for rendering individual pieces outside a wrapper (e.g. docs previews). |
s.sidebar | (side:, variant:, collapsible:, **opts, &block) | The sidebar panel itself. side: is :left (default) or :right. |
s.header | (**opts, &block) | Flex column at the top of the sidebar, typically for a logo or app name. |
s.content | (**opts, &block) | Scrollable flex column that fills the remaining vertical space. |
s.group | (**opts, &block) | A labelled section inside sidebar content, padded on all sides. |
s.group_label | (name = nil, **opts, &block) | Small muted heading above a group of links. |
s.group_content | (**opts, &block) | Wrapper div for the group's menu list. |
s.menu | (**opts, &block) | Unordered list (ul) that holds menu items with gap spacing. |
s.menu_item | (**opts, &block) | List item (li) wrapper for a single menu button. |
s.menu_button | (name, options, active: nil, size: :default, icon: nil, **html_opts, &block) | An anchor styled as a sidebar row. Mirrors link_to. Auto-sets active via current_page?; pass active: true/false to override. size: is :sm, :default, :md, or :lg. |
s.collapsible | (open:, flyout: false, content:, **opts, &block) | sui.collapsible for a sub-menu. With flyout: true the content also opens on hover as a panel to the right of its menu_button while the rail is collapsed to icons — the same menu_sub, written once, headed by the button's label. On the rail the click is inert and the inline open state is kept. |
s.separator | (**opts) | Horizontal rule styled with sidebar border colour, with side margins. |
s.footer | (**opts, &block) | Flex column pinned to the bottom of the sidebar. |
s.inset | (**opts, &block) | The main content area (main) that sits beside the sidebar, grows to fill remaining space. |
s.trigger | (**opts) | Icon button that dispatches shadcnrb--sidebar--component#toggle on click to expand/collapse the sidebar. |
s.rail | (**opts) | Narrow invisible hit-area along the sidebar edge — also toggles on click, visible as a faint line on hover. |