Docs

Cinder UI

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

Release readiness

2 items need review before ship.

Current focus

Ship the refreshed component docs and tighten visual regression coverage.

This week

Sidebar primitives, docs examples, and browser-driven QA.

  <.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

Compact inset content

Collapse the rail by default when the surrounding panel already provides context.

  <.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 state comes from LiveView assigns.

The trigger pushes an event, but the shell stays collapsed until the server sends back `open=true`.

Useful when a layout-level toggle also drives persistence, analytics, or permission-based nav changes.
  <.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

Internal scrolling

Constrain the parent height and the sidebar’s content region becomes internally scrollable.

This pattern works well for nested inspectors, settings panes, or workflow steps embedded in a larger page.
  <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)