Advanced.sidebar_layout
Progressive
Phoenix-first sidebar shell for app layouts.
This component is designed for LiveView and server-rendered Phoenix apps, rather than mirroring shadcn’s React-only primitive structure. Use the :header, :sidebar, :footer, and :main slots as the primary API. sidebar/1 and sidebar_main/1 remain available as lower-level escape hatches when you need manual control.
Use default_open for the default uncontrolled behavior. Pass open to let LiveView control the current state, and pair it with toggle_event if the built-in trigger should push a server event instead of toggling locally.
By default the sidebar shell stretches to the viewport height. Set full_screen={false} when rendering inside a nested panel or container that already manages its own height.
Workspace shell
<.sidebar_layout id="workspace-shell-sidebar" persist_key="docs:workspace-shell">
<:header>
<.sidebar_header>
<button
type="button"
class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-colors"
>
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex size-8 items-center justify-center rounded-lg">
<.icon name="briefcase-business" class="size-4" />
</div>
<div data-sidebar-label class="min-w-0 flex-1">
<p class="truncate text-sm font-medium">Acme Inc</p>
<p class="text-sidebar-foreground/70 truncate text-xs">Enterprise</p>
</div>
<div data-sidebar-label class="text-sidebar-foreground/70 flex flex-col">
<.icon name="chevron-up" class="size-3" />
<.icon name="chevron-down" class="size-3 -mt-1" />
</div>
</button>
</.sidebar_header>
</:header>
<:sidebar>
<.sidebar_group label="Platform">
<.sidebar_item icon="square-play" current={true} collapsible={true} default_open={true}>
Playground
<:children>
<.sidebar_item>History</.sidebar_item>
<.sidebar_item>Starred</.sidebar_item>
<.sidebar_item>Settings</.sidebar_item>
</:children>
</.sidebar_item>
<.sidebar_item icon="bot">Models</.sidebar_item>
<.sidebar_item icon="book-open">Documentation</.sidebar_item>
<.sidebar_item icon="settings-2">Settings</.sidebar_item>
</.sidebar_group>
</:sidebar>
<:footer>
<.sidebar_footer>
<.sidebar_profile_menu
id="workspace-shell-profile-menu"
name="shadcn"
subtitle="m@example.com"
avatar_src="example.png"
avatar_alt="shadcn"
>
<:item icon="badge-check">Account</:item>
<:item icon="credit-card">Billing</:item>
<:item icon="bell">Notifications</:item>
<:item icon="log-out" separator_before={true}>Log out</:item>
</.sidebar_profile_menu>
</.sidebar_footer>
</:footer>
<:main>
<div class="space-y-4">
<div class="flex h-7 items-center">
<.sidebar_trigger />
</div>
<section class="rounded-xl border bg-card p-5">
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="text-sm font-semibold">Release readiness</h3>
<p class="text-muted-foreground mt-1 text-sm">2 items need review before ship.</p>
</div>
<.button size={:sm}>Open queue</.button>
</div>
</section>
<div class="grid gap-4 md:grid-cols-2">
<section class="rounded-xl border bg-card p-4">
<h3 class="text-sm font-semibold">Current focus</h3>
<p class="text-muted-foreground mt-3 text-sm">
Ship the refreshed component docs and tighten visual regression coverage.
</p>
</section>
<section class="rounded-xl border bg-card p-4">
<h3 class="text-sm font-semibold">This week</h3>
<p class="text-muted-foreground mt-3 text-sm">
Sidebar primitives, docs examples, and browser-driven QA.
</p>
</section>
</div>
</div>
</:main>
</.sidebar_layout>
Collapsed by default
<.sidebar_layout id="collapsed-sidebar" default_open={false} full_screen={false}>
<:header>
<.sidebar_header>
<span data-sidebar-label class="text-sm font-semibold">Navigation</span>
</.sidebar_header>
</:header>
<:sidebar>
<.sidebar_group label="Navigation">
<.sidebar_item icon="home" current={true}>Home</.sidebar_item>
<.sidebar_item icon="inbox">Inbox</.sidebar_item>
<.sidebar_item icon="settings">Settings</.sidebar_item>
</.sidebar_group>
</:sidebar>
<:main>
<div class="space-y-4">
<div class="flex h-7 items-center">
<.sidebar_trigger />
</div>
<div class="rounded-xl border bg-card p-4">
<h3 class="text-sm font-semibold">Compact inset content</h3>
<p class="text-muted-foreground mt-2 text-sm">
Collapse the rail by default when the surrounding panel already provides context.
</p>
</div>
</div>
</:main>
</.sidebar_layout>
Server-controlled open state
<.sidebar_layout
id="server-controlled-sidebar"
open={false}
toggle_event="sidebar:set_open"
full_screen={false}
>
<:header>
<.sidebar_header>
<span data-sidebar-label class="text-sm font-semibold">Workspace</span>
<.sidebar_trigger />
</.sidebar_header>
</:header>
<:sidebar>
<.sidebar_group label="Workspace">
<.sidebar_item icon="home" current={true}>Overview</.sidebar_item>
<.sidebar_item icon="inbox">Approvals</.sidebar_item>
<.sidebar_item icon="settings">Settings</.sidebar_item>
</.sidebar_group>
</:sidebar>
<:main>
<div class="space-y-4">
<div class="rounded-xl border bg-card p-4">
<h3 class="text-sm font-semibold">Sidebar state comes from LiveView assigns.</h3>
<p class="text-muted-foreground mt-2 text-sm">
The trigger pushes an event, but the shell stays collapsed until the server sends back `open={true}`.
</p>
</div>
<div class="rounded-xl border border-dashed p-4 text-sm text-muted-foreground">
Useful when a layout-level toggle also drives persistence, analytics, or permission-based nav changes.
</div>
</div>
</:main>
</.sidebar_layout>
Internal scrolling in a nested panel
<div class="h-80 overflow-hidden rounded-xl border">
<.sidebar_layout id="scrolling-sidebar" full_screen={false}>
<:sidebar>
<.sidebar_group label="Large section">
<.sidebar_item :for={index <- 1..14} icon="folder-open">
Project {index}
</.sidebar_item>
</.sidebar_group>
<.sidebar_group label="Pinned">
<.sidebar_item icon="star">Launch checklist</.sidebar_item>
<.sidebar_item icon="clock-3">Weekly review</.sidebar_item>
</.sidebar_group>
</:sidebar>
<:main>
<div class="space-y-4">
<div class="rounded-xl border bg-card p-4">
<h3 class="text-sm font-semibold">Internal scrolling</h3>
<p class="text-muted-foreground mt-2 text-sm">
Constrain the parent height and the sidebar’s content region becomes internally scrollable.
</p>
</div>
<div class="rounded-xl border border-dashed p-4 text-sm text-muted-foreground">
This pattern works well for nested inspectors, settings panes, or workflow steps embedded in a larger page.
</div>
</div>
</:main>
</.sidebar_layout>
</div>
Attributes
| Name | Type | Default | Values | Global Includes |
|---|---|---|---|---|
class
|
:string |
— | — | — |
collapsible
|
:atom |
:icon
|
:icon
,
:none
|
— |
default_open
|
:boolean |
true
|
— | — |
full_screen
|
:boolean |
true
|
— | — |
id
|
:string |
— | — | — |
main_class
|
:string |
— | — | — |
open
|
:boolean |
— | — | — |
persist_key
|
:string |
— | — | — |
rest
|
:global |
— | — | — |
sidebar_class
|
:string |
— | — | — |
sidebar_content_class
|
:string |
— | — | — |
toggle_event
|
:string |
— | — | — |
Slots
| Slot | Slot Attributes |
|---|---|
footer
|
— |
header
|
— |
main
Required
|
class
(:string)
|
sidebar
Required
|
class
(:string)
content_class
(:string)
|