---
title: SidebarFrame
description: Build desktop sidebar navigation that moves into a mobile drawer.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# SidebarFrame

> Build desktop sidebar navigation that moves into a mobile drawer.

`SidebarFrame` is both the state provider and desktop layout root. `SidebarFrame.Sidebar` renders inline on desktop and automatically moves its content into a mobile Sheet on smaller viewports.

## Basic usage

```tsx
import { SidebarFrame } from 'moraine'

export function Example() {
  return (
    <SidebarFrame>
      <SidebarFrame.Sidebar>
        <SidebarFrame.SidebarBody>
          <SidebarFrame.Menu>
            <SidebarFrame.Label>Navigation</SidebarFrame.Label>
            <SidebarFrame.Item leading="i-lucide:home" isActive>
              Home
            </SidebarFrame.Item>
            <SidebarFrame.Item leading="i-lucide:settings">Settings</SidebarFrame.Item>
          </SidebarFrame.Menu>
        </SidebarFrame.SidebarBody>
      </SidebarFrame.Sidebar>
      <SidebarFrame.Main>Content</SidebarFrame.Main>
    </SidebarFrame>
  )
}
```

## Anatomy

```text
SidebarFrame [component; slot=root]
├── SidebarFrame.Sidebar [part; slot=sidebar]
│   ├── SidebarFrame.SidebarHeader [part; slot=sidebarHeader]
│   ├── SidebarFrame.SidebarBody [part; slot=sidebarBody]
│   │   └── SidebarFrame.Menu [part; slot=menu]
│   │       ├── SidebarFrame.Label [part; slot=label]
│   │       ├── SidebarFrame.Item [part; slot=item]
│   │       │   ├── itemLeading [slot]
│   │       │   ├── itemLabel [slot]
│   │       │   ├── itemTrailing [slot]
│   │       │   └── itemActions [slot]
│   │       └── SidebarFrame.Submenu [part; slot=submenu]
│   │           ├── SidebarFrame.SubmenuTrigger [part; slot=submenuTrigger]
│   │           └── SidebarFrame.SubmenuContent [part; slot=submenuContent]
│   └── SidebarFrame.SidebarFooter [part; slot=sidebarFooter]
├── SidebarFrame.Main [part; slot=main]
└── SidebarFrame.Trigger [part]
```

Trigger can appear anywhere inside the root. On mobile, Sidebar renders its regions inside an internal Sheet.

## Navigation menu layer

`SidebarFrame` provides structured menu primitives to build desktop and mobile navigation menus:

- `SidebarFrame.Menu`: Section container for a labeled group of navigation items.
- `SidebarFrame.Label`: Section title with polymorphic tag support (`as="h2"`, `'span'`, etc.).
- `SidebarFrame.Item`: Polymorphic navigation trigger (`<button>`, `<a>`, or custom link component) with built-in `leading`, `trailing`, `actions`, and `isActive`. `leading` / `trailing` accept icon names or arbitrary JSX.
- `SidebarFrame.Submenu` / `SubmenuTrigger` / `SubmenuContent`: Collapsible sub-navigation built on `Collapsible`. Keep links and disclosure controls on separate elements when a parent has both an `href` and children.

## Usage

### Responsive navigation ownership

`SidebarFrame.Sidebar` owns the same navigation content in desktop and mobile layouts. Put the application header outside the frame, then place `SidebarFrame.Trigger` where users need to reveal or collapse navigation. If a child needs to coordinate with the sidebar, read `useSidebarFrame()` within the provider rather than maintaining a second open state.

Desktop and mobile open state are independent. Root accepts controlled `open` / `defaultOpen` / `onOpenChange` for desktop. Mobile sheet state stays internal and resets when returning to desktop. Items with `href` close the mobile sheet on activate; set `closeOnSelect={false}` on an item to keep it open. When `isMobile` is omitted, `breakpoint` (default `768`) drives `(max-width: ${breakpoint}px)` via `matchMedia`.

Root `classes` and `styles` set defaults for frame, sidebar, menu, label, item, and submenu slots. On mobile, the sidebar renders inside a Sheet. Style the SidebarFrame regions through their own slots; SidebarFrame overrides do not style the internal Sheet. See [Theming](https://moraine.subf.dev/docs/theming.md) for nested Provider behavior.

`SidebarFrame` declares `--mo-sidebar-width` on its root. Set global `--sidebar-width` in CSS or with `presetMoraine`. Width classes on `SidebarFrame.Sidebar` take precedence.

The root uses `h-full` so it can sit inside a bounded container. For a viewport shell, pass `class="h-dvh"` (or `h-svh`) on the root.

Call `useSidebarFrame()` from a descendant for `isMobile`, `isOpen`, `state`, `scrolled`, `setOpen`, and `toggle`. A global application header belongs outside the frame. For desktop resizing, branch on `isMobile()` and compose the generic `Resizable` component explicitly.

To persist desktop open state across reloads, control `open` from the app and store it in a cookie (not `localStorage`, which mismatches SSR hydration).

```tsx
import { Avatar, Badge, Breadcrumb, Button, Icon, Kbd, Separator, SidebarFrame } from 'moraine'
import { For, Show } from 'solid-js'

export function ResponsiveUsage() {
  const breadcrumbItems = [
    { label: 'Acme Cloud', href: '#' },
    { label: 'Platform', href: '#' },
    { label: 'Deployments' },
  ]

  const navPlatform = [
    { label: 'Dashboard', icon: 'i-lucide:layout-dashboard', href: '#' },
    { label: 'Deployments', icon: 'i-lucide:rocket', href: '#', active: true, badge: '12' },
    { label: 'Analytics', icon: 'i-lucide:bar-chart-3', href: '#' },
    { label: 'Cloud Logs', icon: 'i-lucide:terminal', href: '#' },
  ]

  const navSettings = [
    { label: 'Team Members', icon: 'i-lucide:users', href: '#' },
    { label: 'API Keys', icon: 'i-lucide:key', href: '#' },
    { label: 'Billing & Usage', icon: 'i-lucide:credit-card', href: '#' },
  ]

  const deployments = [
    { name: 'api-gateway', env: 'Production • us-east-1', status: 'Live', time: '2m ago' },
    { name: 'auth-service', env: 'Staging • eu-west-1', status: 'Building', time: '5m ago' },
    { name: 'worker-pool', env: 'Production • us-west-2', status: 'Live', time: '18m ago' },
  ]

  return (
    <div class="border border-border/70 rounded-xl bg-background h-96 w-full shadow-xs overflow-hidden">
      <SidebarFrame>
        <SidebarFrame.Sidebar class="border-r border-border/60 bg-card/40">
          <SidebarFrame.SidebarHeader class="px-3 border-b border-border/60 flex h-11 items-center justify-between">
            <div class="flex gap-2.5 min-w-0 items-center">
              <div class="text-xs text-primary-foreground font-bold rounded-md bg-primary flex shrink-0 size-7 items-center justify-center">
                <Icon name="i-lucide:command" class="size-4" />
              </div>
              <div class="flex flex-col min-w-0">
                <span class="text-xs leading-tight font-semibold truncate">Acme Cloud</span>
                <span class="text-[10px] text-muted-foreground leading-tight truncate">
                  Production org
                </span>
              </div>
            </div>
            <Badge size="sm" variant="subtle">
              Pro
            </Badge>
          </SidebarFrame.SidebarHeader>

          <SidebarFrame.SidebarBody class="p-2 space-y-3">
            <div class="text-xs text-muted-foreground px-2.5 py-1.5 border border-border/60 rounded-md bg-muted/30 flex w-full items-center justify-between">
              <div class="flex gap-1.5 items-center">
                <Icon name="i-lucide:search" class="opacity-70 size-3.5" />
                <span>Search...</span>
              </div>
              <div class="flex gap-0.5 items-center">
                <Kbd size="sm" value="meta" />
                <Kbd size="sm" value="K" />
              </div>
            </div>

            <SidebarFrame.Menu>
              <SidebarFrame.Label>Platform</SidebarFrame.Label>
              <For each={navPlatform}>
                {(item) => (
                  <SidebarFrame.Item href={item.href} leading={item.icon} isActive={item.active}>
                    <span class="flex-1 truncate">{item.label}</span>
                    <Show when={item.badge}>
                      <Badge size="sm" variant="subtle" class="ml-auto shrink-0">
                        {item.badge}
                      </Badge>
                    </Show>
                  </SidebarFrame.Item>
                )}
              </For>
            </SidebarFrame.Menu>

            <SidebarFrame.Menu>
              <SidebarFrame.Label>Configuration</SidebarFrame.Label>
              <For each={navSettings}>
                {(item) => (
                  <SidebarFrame.Item href={item.href} leading={item.icon}>
                    {item.label}
                  </SidebarFrame.Item>
                )}
              </For>
            </SidebarFrame.Menu>
          </SidebarFrame.SidebarBody>

          <SidebarFrame.SidebarFooter class="p-2 border-t border-border/60 flex items-center justify-between">
            <div class="flex gap-2 min-w-0 items-center">
              <Avatar text="AR" size="sm" />
              <div class="flex flex-col min-w-0">
                <span class="text-xs leading-tight font-medium truncate">Alex Rivers</span>
                <span class="text-[10px] text-muted-foreground leading-tight truncate">
                  alex@acme.com
                </span>
              </div>
            </div>
            <Button variant="ghost" size="icon-xs" aria-label="User preferences">
              <Icon name="i-lucide:ellipsis-vertical" class="size-3.5" />
            </Button>
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>

        <SidebarFrame.Main class="flex flex-col">
          <header class="px-3 border-b border-border/60 flex shrink-0 gap-2 h-11 items-center">
            <SidebarFrame.Trigger
              as={Button}
              variant="ghost"
              size="icon-sm"
              aria-label="Toggle sidebar"
            >
              <Icon name="i-lucide:menu" class="size-4" />
            </SidebarFrame.Trigger>
            <Separator orientation="vertical" class="h-4" />
            <Breadcrumb items={breadcrumbItems} />
            <div class="flex-1" />
            <Button size="xs" leading="i-lucide:plus">
              New Deployment
            </Button>
          </header>

          <div class="p-4 flex-1 space-y-4">
            <div class="gap-2.5 grid grid-cols-3">
              <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
                <div class="text-[11px] text-muted-foreground">Active Services</div>
                <div class="text-base font-bold mt-0.5">24 / 24</div>
              </div>
              <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
                <div class="text-[11px] text-muted-foreground">Global Uptime</div>
                <div class="text-base text-emerald-600 font-bold mt-0.5 dark:text-emerald-400">
                  99.98%
                </div>
              </div>
              <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
                <div class="text-[11px] text-muted-foreground">Avg Latency</div>
                <div class="text-base font-bold mt-0.5">42 ms</div>
              </div>
            </div>

            <div class="border border-border/60 rounded-lg bg-card/40 overflow-hidden">
              <div class="text-xs font-semibold px-3 py-2 border-b border-border/60 flex items-center justify-between">
                <span>Recent Deployments</span>
                <span class="text-[10px] text-muted-foreground font-normal">Auto-synced</span>
              </div>
              <div class="divide-border/40 divide-y">
                <For each={deployments}>
                  {(item) => (
                    <div class="text-xs px-3 py-2 flex items-center justify-between">
                      <div class="flex flex-col">
                        <span class="font-medium">{item.name}</span>
                        <span class="text-[10px] text-muted-foreground">{item.env}</span>
                      </div>
                      <div class="flex gap-2 items-center">
                        <Badge
                          size="sm"
                          variant={item.status === 'Live' ? 'subtle' : 'outline'}
                          leading={
                            item.status === 'Live'
                              ? 'i-lucide:check-circle-2'
                              : 'i-lucide:loader-circle'
                          }
                        >
                          {item.status}
                        </Badge>
                        <span class="text-[10px] text-muted-foreground">{item.time}</span>
                      </div>
                    </div>
                  )}
                </For>
              </div>
            </div>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

## Examples

### Collapsible sub-navigation

Compose `SidebarFrame.Submenu` inside `SidebarFrame.SidebarBody` for multi-level navigation. Use `SubmenuTrigger` for disclosure and a separate `Item` with `href` when the parent label is also a link.

```tsx
import { Badge, Breadcrumb, Button, Icon, Separator, SidebarFrame } from 'moraine'
import { For, createSignal } from 'solid-js'

export function CollapsibleGroups() {
  const [activePage, setActivePage] = createSignal('Pages and Layouts')

  const breadcrumbItems = () => [
    { label: 'Documentation', href: '#' },
    { label: 'Application Architecture', href: '#' },
    { label: activePage() },
  ]

  return (
    <div class="border border-border/70 rounded-xl bg-background h-[420px] w-full shadow-xs overflow-hidden">
      <SidebarFrame isMobile={false}>
        <SidebarFrame.Sidebar class="border-r border-border/60 bg-card/40 w-64">
          <SidebarFrame.SidebarHeader class="px-3 border-b border-border/60 flex h-11 items-center justify-between">
            <div class="text-xs font-bold flex gap-2 items-center">
              <div class="text-white rounded-md bg-emerald-600 flex size-6 items-center justify-center">
                <Icon name="i-lucide:book-open" class="size-3.5" />
              </div>
              <span>Framework Docs</span>
            </div>
            <Badge size="sm" variant="outline">
              v3.0
            </Badge>
          </SidebarFrame.SidebarHeader>

          <SidebarFrame.SidebarBody class="p-2 space-y-3">
            <SidebarFrame.Menu>
              <SidebarFrame.Label class="text-[10px] tracking-wider font-semibold uppercase">
                Overview
              </SidebarFrame.Label>
              <SidebarFrame.Item
                leading="i-lucide:compass"
                isActive={activePage() === 'Getting Started'}
                onClick={() => setActivePage('Getting Started')}
              >
                Getting Started
              </SidebarFrame.Item>
              <SidebarFrame.Item
                leading="i-lucide:rocket"
                isActive={activePage() === 'Quickstart'}
                onClick={() => setActivePage('Quickstart')}
              >
                Quickstart
              </SidebarFrame.Item>
            </SidebarFrame.Menu>

            <SidebarFrame.Menu>
              <SidebarFrame.Label class="text-[10px] tracking-wider font-semibold uppercase">
                Architecture
              </SidebarFrame.Label>
              <SidebarFrame.Submenu defaultOpen transition>
                <SidebarFrame.SubmenuTrigger leading="i-lucide:route">
                  Routing
                </SidebarFrame.SubmenuTrigger>
                <SidebarFrame.SubmenuContent>
                  <For each={['Defining Routes', 'Pages and Layouts', 'Navigation & Links']}>
                    {(item) => (
                      <SidebarFrame.Item
                        isActive={activePage() === item}
                        onClick={() => setActivePage(item)}
                      >
                        {item}
                      </SidebarFrame.Item>
                    )}
                  </For>
                </SidebarFrame.SubmenuContent>
              </SidebarFrame.Submenu>

              <SidebarFrame.Submenu transition>
                <SidebarFrame.SubmenuTrigger leading="i-lucide:database">
                  Data Fetching
                </SidebarFrame.SubmenuTrigger>
                <SidebarFrame.SubmenuContent>
                  <For each={['Server Actions', 'Streaming & Suspense', 'Caching & Revalidation']}>
                    {(item) => (
                      <SidebarFrame.Item
                        isActive={activePage() === item}
                        onClick={() => setActivePage(item)}
                      >
                        {item}
                      </SidebarFrame.Item>
                    )}
                  </For>
                </SidebarFrame.SubmenuContent>
              </SidebarFrame.Submenu>
            </SidebarFrame.Menu>
          </SidebarFrame.SidebarBody>

          <SidebarFrame.SidebarFooter class="text-xs text-muted-foreground p-2.5 border-t border-border/60 flex items-center justify-between">
            <span class="text-[11px]">SolidJS v1.9 Ecosystem</span>
            <Icon name="i-lucide:external-link" class="opacity-60 size-3" />
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>

        <SidebarFrame.Main class="flex flex-col">
          <header class="px-3 border-b border-border/60 flex shrink-0 gap-2 h-11 items-center">
            <SidebarFrame.Trigger
              as={Button}
              variant="ghost"
              size="icon-sm"
              aria-label="Toggle navigation"
            >
              <Icon name="i-lucide:menu" class="size-4" />
            </SidebarFrame.Trigger>
            <Separator orientation="vertical" class="h-4" />
            <Breadcrumb items={breadcrumbItems()} />
          </header>

          <div class="p-5 flex-1 overflow-y-auto space-y-4">
            <div>
              <div class="text-xs text-primary font-semibold">Architecture / Routing</div>
              <h2 class="text-lg text-foreground font-bold mt-0.5">{activePage()}</h2>
              <p class="text-xs text-muted-foreground leading-relaxed mt-1">
                Learn how nested layouts and leaf pages compose seamlessly in modern file-based
                routers without unnecessary re-renders.
              </p>
            </div>

            <div class="text-xs font-mono p-3 border border-border/60 rounded-lg bg-muted/30 space-y-1">
              <div class="text-muted-foreground">// Example page component definition</div>
              <div>
                <span class="text-purple-500">export default function</span>{' '}
                <span class="text-blue-500">Page</span>() {'{'}
              </div>
              <div class="pl-4">
                <span class="text-purple-500">return</span>{' '}
                <span class="text-emerald-600 dark:text-emerald-400">
                  {'<h1>Welcome to '}
                  {activePage()}
                  {'</h1>'}
                </span>
              </div>
              <div>{'}'}</div>
            </div>

            <div class="text-xs pt-3 border-t border-border/50 flex items-center justify-between">
              <Button variant="outline" size="xs" leading="i-lucide:arrow-left">
                Previous: Defining Routes
              </Button>
              <Button size="xs" trailing="i-lucide:arrow-right">
                Next: Navigation & Links
              </Button>
            </div>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

### Side and Variant

Using `side="right"` and `variant="inset"` to build an inspector and properties panel alongside a main document or canvas workspace.

```tsx
import { Avatar, Badge, Button, Icon, Separator, SidebarFrame } from 'moraine'
import { For } from 'solid-js'

export function SideAndVariantUsage() {
  const reviewers = [
    { name: 'Sarah C.', approved: true },
    { name: 'David M.', approved: true },
    { name: 'Kenji S.', approved: false },
  ]

  const tags = ['Architecture', 'RFC', 'High Priority', 'v4.2']

  return (
    <div class="border border-border/70 rounded-xl bg-muted/20 h-96 w-full shadow-xs overflow-hidden">
      <SidebarFrame isMobile={false} side="right" variant="inset">
        <SidebarFrame.Sidebar class="bg-card/60 w-64">
          <SidebarFrame.SidebarHeader class="px-3 border-b border-border/60 flex h-11 items-center justify-between">
            <div class="flex gap-2 items-center">
              <Icon name="i-lucide:sliders-horizontal" class="text-muted-foreground size-3.5" />
              <span class="text-xs font-semibold">Document Inspector</span>
            </div>
            <Button variant="ghost" size="icon-xs" aria-label="Inspector options">
              <Icon name="i-lucide:more-horizontal" class="size-3.5" />
            </Button>
          </SidebarFrame.SidebarHeader>

          <SidebarFrame.SidebarBody class="p-3 space-y-4">
            <div class="space-y-1.5">
              <div class="text-[10px] text-muted-foreground tracking-wider font-semibold uppercase">
                Review Status
              </div>
              <div class="flex gap-2 items-center">
                <Badge variant="subtle" size="sm" leading="i-lucide:git-pull-request">
                  In Review (2/3)
                </Badge>
              </div>
            </div>

            <Separator class="my-2" />

            <div class="space-y-2">
              <div class="text-[10px] text-muted-foreground tracking-wider font-semibold uppercase">
                Document Owner
              </div>
              <div class="flex gap-2 items-center">
                <Avatar text="ER" size="sm" />
                <span class="text-xs font-medium">Elena Rostova</span>
              </div>
            </div>

            <div class="space-y-2">
              <div class="text-[10px] text-muted-foreground tracking-wider font-semibold uppercase">
                Design Reviewers
              </div>
              <div class="space-y-1.5">
                <For each={reviewers}>
                  {(reviewer) => (
                    <div class="text-xs flex items-center justify-between">
                      <span class="text-muted-foreground">{reviewer.name}</span>
                      <Icon
                        name={reviewer.approved ? 'i-lucide:check-circle-2' : 'i-lucide:clock'}
                        class={`size-3.5 ${
                          reviewer.approved
                            ? 'text-emerald-600 dark:text-emerald-400'
                            : 'text-muted-foreground/60'
                        }`}
                      />
                    </div>
                  )}
                </For>
              </div>
            </div>

            <div class="space-y-1.5">
              <div class="text-[10px] text-muted-foreground tracking-wider font-semibold uppercase">
                Tags & Milestones
              </div>
              <div class="flex flex-wrap gap-1">
                <For each={tags}>
                  {(tag) => (
                    <Badge size="sm" variant="outline">
                      {tag}
                    </Badge>
                  )}
                </For>
              </div>
            </div>
          </SidebarFrame.SidebarBody>

          <SidebarFrame.SidebarFooter class="p-3 border-t border-border/60 flex gap-2">
            <Button size="xs" variant="outline" class="flex-1">
              Changes
            </Button>
            <Button size="xs" class="flex-1">
              Approve
            </Button>
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>
        <SidebarFrame.Main class="flex flex-col overflow-hidden">
          <div class="px-4 border-b border-border/60 flex shrink-0 h-11 items-center justify-between">
            <div class="flex gap-2 items-center">
              <Badge variant="subtle" size="sm">
                RFC-104
              </Badge>
              <span class="text-xs text-muted-foreground">Updated 14 minutes ago</span>
            </div>
            <span class="text-[11px] text-muted-foreground font-mono">SHA: 8f42c90</span>
          </div>

          <div class="p-4 flex-1 overflow-y-auto space-y-3">
            <div>
              <h3 class="text-sm text-foreground font-bold">
                Unified Distributed Cache Architecture
              </h3>
              <p class="text-xs text-muted-foreground leading-relaxed mt-1">
                Proposed multi-region cache synchronization protocol for sub-millisecond edge reads
                and automated cache invalidation upon ledger updates.
              </p>
            </div>

            <div class="p-3 border border-border/70 rounded-lg bg-card space-y-2">
              <div class="text-xs font-semibold flex gap-1.5 items-center">
                <Icon name="i-lucide:list-checks" class="text-primary size-4" />
                <span>Verification Requirements</span>
              </div>
              <ul class="text-xs text-muted-foreground pl-5 list-disc space-y-1">
                <li>Under 5ms replication latency across tier-1 regional zones</li>
                <li>Zero cache poisoning on concurrent write split-brain failover</li>
                <li>Linear scaling up to 100,000 requests per node per second</li>
              </ul>
            </div>
          </div>

          <div class="text-xs text-muted-foreground px-4 py-2 border-t border-border/50 flex shrink-0 items-center justify-between">
            <span>Author: elena.rostova@distributed.io</span>
            <span>Target: v4.2-LTS</span>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

### Header and footer slots

Compose compound header and footer slots with `DropdownMenu` to provide an interactive team/workspace switcher and account profile menu.

```tsx
import { Avatar, Badge, Button, DropdownMenu, Icon, SidebarFrame } from 'moraine'
import { For, createSignal } from 'solid-js'

export function HeaderFooterSlots() {
  const [currentTeam, setCurrentTeam] = createSignal('Acme Corp')
  const [currentProject, setCurrentProject] = createSignal('Core Engine v2')

  const teamItems = [
    {
      label: 'Acme Corp',
      icon: 'i-lucide:building-2',
      onSelect: () => setCurrentTeam('Acme Corp'),
    },
    {
      label: 'Acme Labs',
      icon: 'i-lucide:flask-conical',
      onSelect: () => setCurrentTeam('Acme Labs'),
    },
    {
      label: 'Personal Space',
      icon: 'i-lucide:user',
      onSelect: () => setCurrentTeam('Personal Space'),
    },
    { type: 'separator' as const },
    {
      label: 'Add Workspace',
      icon: 'i-lucide:plus',
      onSelect: () => {},
    },
  ]

  const userItems = [
    { label: 'Account Settings', icon: 'i-lucide:settings' },
    { label: 'Billing & Invoices', icon: 'i-lucide:credit-card' },
    { label: 'Notifications', icon: 'i-lucide:bell' },
    { type: 'separator' as const },
    { label: 'Log out', icon: 'i-lucide:log-out', variant: 'destructive' as const },
  ]

  const projects = [
    { name: 'Core Engine v2', role: 'Architecture Lead', icon: 'i-lucide:cpu', branch: 'main' },
    {
      name: 'Design System',
      role: 'Core Maintainer',
      icon: 'i-lucide:palette',
      branch: 'release/v3',
    },
    {
      name: 'Customer Portal',
      role: 'Reviewer',
      icon: 'i-lucide:layout-template',
      branch: 'feature/auth',
    },
    {
      name: 'Analytics Pipeline',
      role: 'Contributor',
      icon: 'i-lucide:bar-chart-2',
      branch: 'trunk',
    },
  ]

  return (
    <div class="border border-border/70 rounded-xl bg-background h-96 w-full shadow-xs overflow-hidden">
      <SidebarFrame isMobile={false}>
        <SidebarFrame.Sidebar class="border-r border-border/60 bg-card/40 w-60">
          {/* Header slot: Team Switcher Dropdown */}
          <SidebarFrame.SidebarHeader class="p-1.5 border-b border-border/60 flex h-12 items-center">
            <DropdownMenu>
              <DropdownMenu.Trigger
                as={Button}
                variant="ghost"
                class="px-2 py-1.5 h-auto w-full justify-between"
                trailing="i-lucide:chevrons-up-down"
              >
                <div class="text-left flex gap-2.5 min-w-0 items-center">
                  <div class="text-xs text-primary-foreground font-bold rounded-md bg-primary flex shrink-0 size-7 items-center justify-center">
                    <Icon name="i-lucide:gallery-vertical-end" class="size-3.5" />
                  </div>
                  <div class="flex flex-col min-w-0">
                    <span class="text-xs leading-tight font-semibold truncate">
                      {currentTeam()}
                    </span>
                    <span class="text-[10px] text-muted-foreground leading-tight truncate">
                      Enterprise Tier
                    </span>
                  </div>
                </div>
              </DropdownMenu.Trigger>
              <DropdownMenu.Content items={teamItems} />
            </DropdownMenu>
          </SidebarFrame.SidebarHeader>

          {/* Body slot: Projects list */}
          <SidebarFrame.SidebarBody class="p-2">
            <SidebarFrame.Menu>
              <SidebarFrame.Label class="text-[10px] tracking-wider font-semibold flex uppercase items-center justify-between">
                <span>Active Projects</span>
                <Badge size="sm" variant="outline">
                  {projects.length}
                </Badge>
              </SidebarFrame.Label>
              <For each={projects}>
                {(proj) => (
                  <SidebarFrame.Item
                    leading={proj.icon}
                    isActive={currentProject() === proj.name}
                    onClick={() => setCurrentProject(proj.name)}
                  >
                    {proj.name}
                  </SidebarFrame.Item>
                )}
              </For>
            </SidebarFrame.Menu>
          </SidebarFrame.SidebarBody>

          {/* Footer slot: User Profile Dropdown */}
          <SidebarFrame.SidebarFooter class="p-2 border-t border-border/60">
            <DropdownMenu>
              <DropdownMenu.Trigger
                as={Button}
                variant="ghost"
                class="px-2 py-1.5 h-auto w-full justify-between"
                trailing="i-lucide:chevrons-up-down"
              >
                <div class="text-left flex gap-2 min-w-0 items-center">
                  <Avatar text="SC" size="sm" />
                  <div class="flex flex-col min-w-0">
                    <span class="text-xs leading-tight font-medium truncate">Sarah Connor</span>
                    <span class="text-[10px] text-muted-foreground leading-tight truncate">
                      sarah@acme.com
                    </span>
                  </div>
                </div>
              </DropdownMenu.Trigger>
              <DropdownMenu.Content items={userItems} />
            </DropdownMenu>
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>

        <SidebarFrame.Main class="flex flex-col overflow-hidden">
          <div class="px-5 border-b border-border/60 flex shrink-0 h-12 items-center justify-between">
            <div class="flex gap-2 items-center">
              <Icon name="i-lucide:folder-git-2" class="text-primary size-4" />
              <h3 class="text-sm font-bold">{currentProject()}</h3>
            </div>
            <Badge size="sm" variant="subtle">
              Active Repository
            </Badge>
          </div>

          <div class="p-5 flex-1 overflow-y-auto space-y-4">
            <p class="text-xs text-muted-foreground leading-relaxed">
              Real-time pipeline orchestration and workload routing system. Synchronized across
              production worker fleets.
            </p>

            <div class="gap-3 grid grid-cols-2">
              <div class="p-3 border border-border/60 rounded-lg bg-card/50">
                <div class="text-[11px] text-muted-foreground">Current Branch</div>
                <div class="text-xs font-bold font-mono mt-1 flex gap-1.5 items-center">
                  <Icon name="i-lucide:git-branch" class="text-primary size-3" />
                  main
                </div>
              </div>
              <div class="p-3 border border-border/60 rounded-lg bg-card/50">
                <div class="text-[11px] text-muted-foreground">Open Pull Requests</div>
                <div class="text-xs font-bold mt-1 flex gap-1.5 items-center">
                  <Icon name="i-lucide:git-pull-request" class="text-emerald-600 size-3" />4 ready
                  for merge
                </div>
              </div>
            </div>
          </div>

          <div class="text-xs text-muted-foreground px-5 py-2.5 border-t border-border/50 flex shrink-0 items-center justify-between">
            <span>Workspace: {currentTeam()}</span>
            <span>Last synced: just now</span>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

### Custom Frame

Embed `SidebarFrame` beneath an application-level global topbar containing brand identification, project pickers, and global alerts.

```tsx
import { Avatar, Badge, Button, Icon, SidebarFrame } from 'moraine'
import { For } from 'solid-js'

export function FrameRender() {
  const cloudNav = [
    { label: 'Kubernetes Clusters', icon: 'i-lucide:server', active: true },
    { label: 'Serverless Functions', icon: 'i-lucide:zap' },
    { label: 'Edge Storage', icon: 'i-lucide:database' },
    { label: 'Virtual Networks', icon: 'i-lucide:network' },
    { label: 'IAM & Security', icon: 'i-lucide:shield-check' },
    { label: 'Metrics & Observability', icon: 'i-lucide:activity' },
  ]

  const clusters = [
    { name: 'k8s-prod-primary', nodes: '48 nodes', region: 'us-east-1', health: 'Healthy' },
    { name: 'ingress-edge-mesh', nodes: '16 nodes', region: 'global-anycast', health: 'Healthy' },
    { name: 'db-replica-pool', nodes: '24 nodes', region: 'eu-west-1', health: 'Degraded' },
  ]

  return (
    <div class="border border-border/70 rounded-xl bg-background flex flex-col h-[400px] w-full shadow-xs overflow-hidden">
      {/* Global Topbar outside the SidebarFrame */}
      <header class="px-4 border-b border-border/70 bg-card/70 flex shrink-0 h-11 items-center justify-between">
        <div class="flex gap-3 items-center">
          <div class="text-xs font-bold flex gap-2 items-center">
            <div class="text-white rounded-md bg-blue-600 flex size-6 items-center justify-center">
              <Icon name="i-lucide:cloud" class="size-3.5" />
            </div>
            <span>HyperScale Cloud</span>
          </div>

          <div class="bg-border/60 h-4 w-px" />

          <button class="text-xs text-muted-foreground font-medium px-2 py-1 rounded-md bg-muted/50 flex gap-1.5 transition-colors items-center hover:text-foreground hover:bg-muted">
            <span>Project: prod-omega</span>
            <Icon name="i-lucide:chevron-down" class="size-3" />
          </button>
        </div>

        <div class="flex gap-2 items-center">
          <Button variant="ghost" size="icon-xs" aria-label="Cloud alarms">
            <div class="relative">
              <Icon name="i-lucide:bell" class="size-3.5" />
              <span class="rounded-full bg-amber-500 size-1.5 absolute -right-0.5 -top-0.5" />
            </div>
          </Button>
          <Avatar text="HC" size="sm" />
        </div>
      </header>

      {/* Embedded SidebarFrame taking remaining height */}
      <SidebarFrame isMobile={false} class="flex-1 h-auto min-h-0">
        <SidebarFrame.Sidebar class="border-r border-border/60 bg-card/30 w-56">
          <SidebarFrame.SidebarBody class="p-2">
            <SidebarFrame.Menu>
              <SidebarFrame.Label class="text-[10px] tracking-wider font-semibold uppercase">
                Infrastructure
              </SidebarFrame.Label>
              <For each={cloudNav}>
                {(item) => (
                  <SidebarFrame.Item href="#" leading={item.icon} isActive={item.active}>
                    {item.label}
                  </SidebarFrame.Item>
                )}
              </For>
            </SidebarFrame.Menu>
          </SidebarFrame.SidebarBody>

          <SidebarFrame.SidebarFooter class="text-[11px] text-muted-foreground p-2 border-t border-border/60 flex items-center justify-between">
            <span>Region: us-east-1</span>
            <span class="rounded-full bg-emerald-500 size-2" />
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>

        <SidebarFrame.Main class="p-4 overflow-y-auto space-y-4">
          <div class="flex items-center justify-between">
            <div>
              <h3 class="text-sm font-bold">Active Kubernetes Clusters</h3>
              <p class="text-xs text-muted-foreground">
                Manage and observe distributed workload clusters
              </p>
            </div>
            <Button size="xs" leading="i-lucide:plus">
              Provision Cluster
            </Button>
          </div>

          <div class="gap-3 grid grid-cols-3">
            <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
              <div class="text-[11px] text-muted-foreground">Total CPU Allocation</div>
              <div class="text-base font-bold mt-0.5">74%</div>
              <div class="mt-2 rounded-full bg-muted h-1.5 w-full overflow-hidden">
                <div class="rounded-full bg-blue-600 h-full w-3/4" />
              </div>
            </div>
            <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
              <div class="text-[11px] text-muted-foreground">Cluster Memory Pool</div>
              <div class="text-base font-bold mt-0.5">61%</div>
              <div class="mt-2 rounded-full bg-muted h-1.5 w-full overflow-hidden">
                <div class="rounded-full bg-indigo-600 h-full w-3/5" />
              </div>
            </div>
            <div class="p-2.5 border border-border/60 rounded-lg bg-card/60">
              <div class="text-[11px] text-muted-foreground">Network Ingress</div>
              <div class="text-base font-bold mt-0.5">1.4 Gbps</div>
              <div class="mt-2 rounded-full bg-muted h-1.5 w-full overflow-hidden">
                <div class="rounded-full bg-emerald-600 h-full w-2/5" />
              </div>
            </div>
          </div>

          <div class="border border-border/60 rounded-lg bg-card/50 overflow-hidden">
            <div class="divide-border/40 divide-y">
              <For each={clusters}>
                {(cluster) => (
                  <div class="text-xs px-3 py-2.5 flex items-center justify-between">
                    <div class="flex gap-2.5 items-center">
                      <Icon name="i-lucide:server" class="text-muted-foreground size-4" />
                      <div>
                        <div class="font-semibold">{cluster.name}</div>
                        <div class="text-[10px] text-muted-foreground">
                          {cluster.nodes} • {cluster.region}
                        </div>
                      </div>
                    </div>
                    <Badge
                      size="sm"
                      variant={cluster.health === 'Healthy' ? 'subtle' : 'outline'}
                      leading={
                        cluster.health === 'Healthy'
                          ? 'i-lucide:check-circle-2'
                          : 'i-lucide:alert-triangle'
                      }
                    >
                      {cluster.health}
                    </Badge>
                  </div>
                )}
              </For>
            </div>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

### Mobile Sheet and desktop Resizable

Combine `SidebarFrame` with the `Resizable` primitive to provide interactive panel sizing on desktop while automatically falling back to an accessible mobile Sheet on compact viewports.

```tsx
import { Button, Icon, Resizable, SidebarFrame, useSidebarFrame } from 'moraine'
import { For, Show } from 'solid-js'

function FileExplorerContent() {
  const files = [
    { name: 'src', type: 'folder-open', level: 0 },
    { name: 'components', type: 'folder', level: 1 },
    { name: 'navigation', type: 'folder-open', level: 1 },
    { name: 'sidebar-frame.tsx', type: 'file-code', level: 2, active: true },
    { name: 'sidebar-frame.recipe.ts', type: 'file-code', level: 2 },
    { name: 'index.ts', type: 'file-code', level: 1 },
    { name: 'package.json', type: 'file-json', level: 0 },
    { name: 'README.md', type: 'file-text', level: 0 },
  ]

  const getIcon = (type: string) => {
    switch (type) {
      case 'folder':
        return 'i-lucide:folder'
      case 'folder-open':
        return 'i-lucide:folder-open'
      case 'file-json':
        return 'i-lucide:file-json'
      case 'file-text':
        return 'i-lucide:file-text'
      default:
        return 'i-lucide:file-code'
    }
  }

  return (
    <>
      <SidebarFrame.SidebarHeader class="px-3 border-b border-border/60 flex h-9 items-center justify-between">
        <span class="text-[11px] text-muted-foreground tracking-wider font-bold uppercase">
          Explorer: Moraine
        </span>
        <div class="flex gap-0.5 items-center">
          <Button variant="ghost" size="icon-xs" aria-label="New file">
            <Icon name="i-lucide:file-plus" class="text-muted-foreground size-3" />
          </Button>
          <Button variant="ghost" size="icon-xs" aria-label="Refresh explorer">
            <Icon name="i-lucide:refresh-cw" class="text-muted-foreground size-3" />
          </Button>
        </div>
      </SidebarFrame.SidebarHeader>

      <SidebarFrame.SidebarBody class="p-1 space-y-0.5">
        <For each={files}>
          {(file) => (
            <div
              class={`text-xs px-2 py-1 rounded flex gap-2 cursor-pointer transition-colors items-center ${
                file.active
                  ? 'bg-accent text-accent-foreground font-medium'
                  : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
              }`}
              style={{ 'padding-left': `${(file.level + 1) * 8}px` }}
            >
              <Icon
                name={getIcon(file.type)}
                class={`shrink-0 size-3.5 ${file.type.startsWith('folder') ? 'text-amber-500' : 'text-blue-500'}`}
              />
              <span class="truncate">{file.name}</span>
            </div>
          )}
        </For>
      </SidebarFrame.SidebarBody>
    </>
  )
}

function EditorContent() {
  return (
    <SidebarFrame.Main class="bg-card/30 flex flex-col h-full">
      {/* Tab bar */}
      <div class="text-xs border-b border-border/60 bg-muted/40 flex shrink-0 h-9 items-center">
        <div class="font-medium px-3 border-r border-border/60 border-t-2 border-t-primary bg-background flex gap-2 h-full items-center">
          <Icon name="i-lucide:file-code" class="text-blue-500 size-3.5" />
          <span>sidebar-frame.tsx</span>
          <Icon name="i-lucide:x" class="opacity-60 size-3 cursor-pointer hover:opacity-100" />
        </div>
        <div class="text-muted-foreground px-3 py-1.5 border-r border-border/60 flex gap-2 items-center">
          <Icon name="i-lucide:file-code" class="opacity-60 size-3.5" />
          <span>index.ts</span>
        </div>
      </div>

      {/* Editor code area */}
      <div class="text-xs font-mono p-4 bg-background/50 flex-1 overflow-auto space-y-1">
        <div class="text-muted-foreground/60">// Moraine Responsive SidebarFrame Primitive</div>
        <div>
          <span class="text-purple-500">export function</span>{' '}
          <span class="text-blue-500 font-semibold">SidebarFrame</span>
          <span class="text-muted-foreground">(props: SidebarFrameProps)</span> {'{'}
        </div>
        <div class="pl-4">
          <span class="text-purple-500">const</span> [local, rest] ={' '}
          <span class="text-blue-500">splitProps</span>
          (props, ['variant', 'side', 'isMobile'])
        </div>
        <div class="pl-4">
          <span class="text-purple-500">const</span> resolved ={' '}
          <span class="text-blue-500">createStyles</span>
          (sidebarFrameRecipe, local)
        </div>
        <div class="pl-4">
          <span class="text-purple-500">return</span> (
        </div>
        <div class="text-emerald-600 pl-8 dark:text-emerald-400">
          {'<SidebarFrameProvider value={context}>'}
        </div>
        <div class="pl-12">{'<div data-slot="sidebar-frame" {...rest}>'}</div>
        <div class="pl-16">{'{local.children}'}</div>
        <div class="pl-12">{'</div>'}</div>
        <div class="text-emerald-600 pl-8 dark:text-emerald-400">{'</SidebarFrameProvider>'}</div>
        <div class="pl-4">)</div>
        <div>{'}'}</div>
      </div>

      {/* Status Bar */}
      <div class="text-[11px] text-muted-foreground px-3 py-1 border-t border-border/60 bg-muted/40 flex shrink-0 items-center justify-between">
        <div class="flex gap-3 items-center">
          <span class="text-foreground font-medium flex gap-1 items-center">
            <Icon name="i-lucide:git-branch" class="size-3" />
            main*
          </span>
          <span>0 errors, 0 warnings</span>
        </div>
        <div class="flex gap-3 items-center">
          <span>TypeScript 5.8</span>
          <span>UTF-8</span>
          <span>Ln 12, Col 24</span>
        </div>
      </div>
    </SidebarFrame.Main>
  )
}

function ResizableLayout() {
  const frame = useSidebarFrame()

  return (
    <Show
      when={frame.isMobile()}
      fallback={
        <Resizable class="flex-1 h-full" defaultValue={['32%', '68%']}>
          <Resizable.Panel min={180} max={360} collapsible collapsibleMin={56}>
            <SidebarFrame.Sidebar class="border-0 bg-card/30 max-w-none! w-full!">
              <FileExplorerContent />
            </SidebarFrame.Sidebar>
          </Resizable.Panel>
          <Resizable.Handle action="collapse">
            {(state) => (
              <Icon
                name={state.collapsed ? 'i-lucide:panel-left-open' : 'i-lucide:panel-left-close'}
              />
            )}
          </Resizable.Handle>
          <Resizable.Panel>
            <EditorContent />
          </Resizable.Panel>
        </Resizable>
      }
    >
      <SidebarFrame.Sidebar class="bg-card/40">
        <FileExplorerContent />
      </SidebarFrame.Sidebar>
      <EditorContent />
    </Show>
  )
}

export function SheetResizableRender() {
  return (
    <div class="border border-border/70 rounded-xl bg-background h-96 w-full shadow-xs overflow-hidden">
      <SidebarFrame>
        <ResizableLayout />
      </SidebarFrame>
    </div>
  )
}
```

### Forced mobile mode

Force `isMobile` mode to demonstrate touch-first layouts, quick action cards, and slide-over navigation drawers on compact screens.

```tsx
import { Avatar, Badge, Button, Icon, SidebarFrame } from 'moraine'
import { For, Show } from 'solid-js'

export function ForcedMobile() {
  const mobileNav = [
    { label: 'Overview', icon: 'i-lucide:home', active: true },
    { label: 'Cards & Wallets', icon: 'i-lucide:credit-card', badge: '3' },
    { label: 'Transfers & Payments', icon: 'i-lucide:arrow-left-right' },
    { label: 'Analytics & Insights', icon: 'i-lucide:pie-chart' },
    { label: 'Security & Limits', icon: 'i-lucide:shield-check' },
    { label: 'Customer Support', icon: 'i-lucide:message-square' },
  ]

  const transactions = [
    { name: 'Stripe Payout', type: 'Deposit', amount: '+$3,450.00', positive: true },
    { name: 'Figma Subscription', type: 'Software', amount: '-$15.00', positive: false },
    { name: 'Coffee Roasters', type: 'Food & Dining', amount: '-$6.25', positive: false },
  ]

  return (
    <div class="mx-auto b b-border flex flex-col h-150 max-w-xs w-full overflow-hidden">
      <SidebarFrame isMobile>
        <SidebarFrame.Sidebar class="bg-card">
          <SidebarFrame.SidebarHeader class="px-4 py-3 border-b border-border/60 flex items-center justify-between">
            <div class="text-sm font-bold flex gap-2 items-center">
              <div class="text-white rounded-lg bg-emerald-600 flex size-7 items-center justify-center">
                <Icon name="i-lucide:wallet" class="size-4" />
              </div>
              <span>PocketPay</span>
            </div>
            <Badge size="sm" variant="subtle">
              v1.8
            </Badge>
          </SidebarFrame.SidebarHeader>

          <SidebarFrame.SidebarBody class="p-3 space-y-1">
            <div class="text-[10px] text-muted-foreground tracking-wider font-semibold px-2 py-1 uppercase">
              Navigation
            </div>
            <For each={mobileNav}>
              {(item) => (
                <a
                  href="#"
                  class={`text-xs font-medium px-3 py-2 rounded-lg flex transition-colors items-center justify-between ${
                    item.active
                      ? 'bg-accent text-accent-foreground font-semibold'
                      : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
                  }`}
                >
                  <div class="flex gap-2.5 items-center">
                    <Icon name={item.icon} class="shrink-0 size-4" />
                    <span>{item.label}</span>
                  </div>
                  <Show when={item.badge}>
                    <Badge size="sm" variant="subtle">
                      {item.badge}
                    </Badge>
                  </Show>
                </a>
              )}
            </For>
          </SidebarFrame.SidebarBody>

          <SidebarFrame.SidebarFooter class="p-3 border-t border-border/60 flex items-center justify-between">
            <div class="flex gap-2 items-center">
              <Avatar text="ML" size="sm" />
              <div class="flex flex-col">
                <span class="text-xs font-medium">Marcus Lee</span>
                <span class="text-[10px] text-muted-foreground">Premier Account</span>
              </div>
            </div>
            <Button variant="ghost" size="icon-xs" aria-label="Sign out">
              <Icon name="i-lucide:log-out" class="size-3.5" />
            </Button>
          </SidebarFrame.SidebarFooter>
        </SidebarFrame.Sidebar>

        <SidebarFrame.Main class="bg-muted/10 flex flex-col h-full">
          {/* Mobile Shell Header */}
          <header class="px-3 py-2 border-b border-border/60 bg-background/80 flex shrink-0 items-center justify-between backdrop-blur">
            <SidebarFrame.Trigger
              as={Button}
              variant="ghost"
              size="icon-sm"
              aria-label="Open mobile menu"
            >
              <Icon name="i-lucide:menu" class="size-4" />
            </SidebarFrame.Trigger>

            <span class="text-xs font-semibold">PocketPay</span>

            <Button variant="ghost" size="icon-sm" aria-label="Notifications">
              <div class="relative">
                <Icon name="i-lucide:bell" class="size-4" />
                <span class="rounded-full bg-emerald-500 size-1.5 absolute -right-0.5 -top-0.5" />
              </div>
            </Button>
          </header>

          {/* Mobile Screen Body */}
          <div class="p-3 flex-1 overflow-y-auto space-y-3">
            {/* Balance Card */}
            <div class="text-white p-3.5 rounded-xl shadow-sm from-emerald-600 to-teal-700 bg-gradient-to-br space-y-2">
              <div class="text-[11px] opacity-80">Available Balance</div>
              <div class="text-xl tracking-tight font-bold">$24,580.45</div>
              <div class="text-[10px] opacity-90 flex gap-1 items-center">
                <Icon name="i-lucide:trending-up" class="size-3" />
                <span>+12.4% from last month</span>
              </div>
            </div>

            {/* Quick Actions */}
            <div class="text-center gap-2 grid grid-cols-3">
              <button class="text-xs p-2 border border-border/60 rounded-lg bg-card flex flex-col gap-1 transition-colors items-center hover:bg-muted/60">
                <Icon name="i-lucide:arrow-up-right" class="text-emerald-600 size-4" />
                <span class="text-[11px] font-medium">Send</span>
              </button>
              <button class="text-xs p-2 border border-border/60 rounded-lg bg-card flex flex-col gap-1 transition-colors items-center hover:bg-muted/60">
                <Icon name="i-lucide:arrow-down-left" class="text-teal-600 size-4" />
                <span class="text-[11px] font-medium">Receive</span>
              </button>
              <button class="text-xs p-2 border border-border/60 rounded-lg bg-card flex flex-col gap-1 transition-colors items-center hover:bg-muted/60">
                <Icon name="i-lucide:credit-card" class="text-blue-600 size-4" />
                <span class="text-[11px] font-medium">Cards</span>
              </button>
            </div>

            {/* Recent Activity */}
            <div class="pt-1 space-y-1.5">
              <div class="text-[10px] text-muted-foreground tracking-wider font-semibold uppercase">
                Recent Transactions
              </div>
              <div class="border border-border/60 rounded-lg bg-card overflow-hidden divide-border/40 divide-y">
                <For each={transactions}>
                  {(tx) => (
                    <div class="text-xs p-2 flex items-center justify-between">
                      <div>
                        <div class="font-medium">{tx.name}</div>
                        <div class="text-[10px] text-muted-foreground">{tx.type}</div>
                      </div>
                      <span
                        class={`font-semibold ${
                          tx.positive ? 'text-emerald-600 dark:text-emerald-400' : 'text-foreground'
                        }`}
                      >
                        {tx.amount}
                      </span>
                    </div>
                  )}
                </For>
              </div>
            </div>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-mobile` | `sidebar-frame`, `sidebar-frame-sidebar`, `sidebar-frame-item` | Present on the mobile rendering path. |
| `data-side` | `sidebar-frame`, `sidebar-frame-sidebar` | Stores the resolved floating or drawer content side. |
| `data-variant` | `sidebar-frame`, `sidebar-frame-sidebar` | Stores the resolved visual variant. |
| `data-closed` | `sidebar-frame-sidebar`, `sidebar-frame-trigger` | Present when disclosure or transition content is closed. |
| `data-expanded` | `sidebar-frame-sidebar`, `sidebar-frame-item-trailing` | Present when the panel, accordion, or menu is expanded. |
| `data-disabled` | `sidebar-frame-item`, `sidebar-frame-trigger` | Present when the component, slot, or item is disabled. |
| `data-open` | `sidebar-frame-trigger` | Present when the disclosure, popover, or overlay is open. |
| `data-active` | `sidebar-frame-item` | Present when the item or trigger is active. |
| `data-with-actions` | `sidebar-frame-item` | — |

## Props

### SidebarFrame

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| breakpoint | number \| undefined | 768 | Max viewport width in pixels that counts as mobile.<br>Used as `(max-width: ${breakpoint}px)` when `isMobile` is omitted. |
| defaultOpen | boolean \| undefined | true | Default open state on desktop. |
| isMobile | boolean \| undefined | — | Controlled mobile mode. When omitted, `matchMedia` uses `breakpoint`. |
| onOpenChange | ((open: boolean) => void) \| undefined | — | Callback invoked when desktop open state changes. |
| open | boolean \| undefined | — | Controlled open state on desktop. |
| scrollThreshold | number \| undefined | 60 | Main scroll offset that changes `scrolled` to true. |
| side | 'left' \| 'right' \| undefined | 'left' | Side occupied by the sidebar. |
| sidebarId | string \| undefined | — | Stable identifier used for associating Trigger `aria-controls` with Sidebar. |
| variant | 'default' \| 'floating' \| 'inset' \| undefined | 'default' | — |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |

### SidebarFrame.Sidebar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| ariaLabel | string \| undefined | 'Sidebar navigation' | Accessible name for the mobile navigation Sheet. Native aria-label takes precedence;<br>title is used when neither native aria-label nor this prop is provided. |
| as | T \| undefined | 'aside' | — |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.SidebarHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.SidebarBody

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.SidebarFooter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Main

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| onScroll | JSX.EventHandlerUnion<HTMLElement, UIEvent> \| undefined | — | — |
| as | T \| undefined | 'div' | — |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Trigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| disabled | boolean \| undefined | — | Whether this trigger is disabled. |
| ref | Ref<T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T]: HTMLElement> \| undefined | — | — |
| as | T \| undefined | 'button' | Element or component to render as. |
| children | JSX.Element \| undefined | — | Trigger label and visual content. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Menu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Label

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | 'div' | — |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Item

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| actions | JSX.Element \| undefined | — | Trailing actions (e.g. action buttons or dropdown menu) rendered alongside the item. |
| closeOnSelect | boolean \| undefined | — | Whether activating this item on mobile closes the sheet.<br>When omitted, items with `href` close the sheet. |
| disabled | boolean \| undefined | — | Whether this trigger is disabled. |
| href | string \| undefined | — | — |
| isActive | boolean \| undefined | — | — |
| leading | IconT.Name \| undefined | — | — |
| ref | Ref<T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T]: HTMLElement> \| undefined | — | — |
| trailing | IconT.Name \| undefined | — | — |
| as | T \| undefined | 'button' | Element or component to render as. |
| children | JSX.Element \| undefined | — | Trigger label and visual content. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.Submenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| defaultOpen | boolean \| undefined | — | — |
| disabled | boolean \| undefined | — | — |
| onOpenChange | ((open: boolean) => void) \| undefined | — | — |
| open | boolean \| undefined | — | — |
| transition | boolean \| undefined | — | — |
| unmountOnHide | boolean \| undefined | — | — |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.SubmenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| disabled | boolean \| undefined | — | Whether this trigger is disabled. |
| leading | IconT.Name \| undefined | — | — |
| ref | Ref<T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T]: HTMLElement> \| undefined | — | — |
| trailing | IconT.Name \| undefined | — | — |
| as | T \| undefined | 'button' | Element or component to render as. |
| children | JSX.Element \| undefined | — | Trigger label and visual content. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### SidebarFrame.SubmenuContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
