Skip to main content

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#

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>
  )
}

Playground#

Props
Slots

Anatomy#

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.

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

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">
                  [email protected]
                </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.

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.

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: [email protected]</span>
            <span>Target: v4.2-LTS</span>
          </div>
        </SidebarFrame.Main>
      </SidebarFrame>
    </div>
  )
}

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

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">
                      [email protected]
                    </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.

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.

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.

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
data-mobileSlot: sidebar-frame, sidebar-frame-sidebar, sidebar-frame-itemDescription: Present on the mobile rendering path.
data-sideSlot: sidebar-frame, sidebar-frame-sidebarDescription: Stores the resolved floating or drawer content side.
data-variantSlot: sidebar-frame, sidebar-frame-sidebarDescription: Stores the resolved visual variant.
data-closedSlot: sidebar-frame-sidebar, sidebar-frame-triggerDescription: Present when disclosure or transition content is closed.
data-expandedSlot: sidebar-frame-sidebar, sidebar-frame-item-trailingDescription: Present when the panel, accordion, or menu is expanded.
data-disabledSlot: sidebar-frame-item, sidebar-frame-triggerDescription: Present when the component, slot, or item is disabled.
data-openSlot: sidebar-frame-triggerDescription: Present when the disclosure, popover, or overlay is open.
data-activeSlot: sidebar-frame-itemDescription: Present when the item or trigger is active.
data-with-actionsSlot: sidebar-frame-itemDescription: —

Props#

SidebarFrame#

Renders a <div> element by default.

Prop

Sidebar#

Renders a <aside> element by default.

Prop

SidebarHeader#

Renders a <div> element by default.

Prop

SidebarBody#

Renders a <div> element by default.

Prop

Renders a <div> element by default.

Prop

Main#

Renders a <div> element by default.

Prop

Trigger#

Renders a <button> element by default.

Prop

Menu#

Renders a <div> element by default.

Prop

Label#

Renders a <div> element by default.

Prop

Item#

Renders a <button> element by default.

Prop

Submenu#

Renders a <div> element by default.

Prop

SubmenuTrigger#

Renders a <button> element by default.

Prop

SubmenuContent#

Renders a <div> element by default.

Prop