Marble UI
← Components

Sidebar

Preview: Plain

App navigation column. Items are links (aria-current marks the active one); groups collapse; whole bar can shrink to an icon rail.

Import

import { Sidebar, SidebarSection, SidebarItem, SidebarGroup } from '@marbleui/ui'

Props

<Sidebar>

PropTypeDefaultDescription
children *requiredReactNode—
classNamestring—
collapsedboolean—Controlled collapse (icon-only rail).
collapsibleboolean—Show the built-in collapse toggle.
defaultCollapsedbooleanfalse
footerReactNode—
headerReactNode—
onCollapsedChange(collapsed: boolean) => void—
variant'floating' | 'plain''plain'

<SidebarSection>

PropTypeDefaultDescription
children *requiredReactNode—
titleReactNode—

<SidebarItem>

PropTypeDefaultDescription
children *requiredstring—
icon *requiredReactNode—
badgeReactNode—
isCurrentboolean—

Also accepts React Aria props (103).

<SidebarGroup>

PropTypeDefaultDescription
children *requiredReactNode—
icon *requiredReactNode—
title *requiredstring—
defaultExpandedboolean—

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Button · React Aria Disclosure · React Aria DisclosurePanel · React Aria Link. Follow the primitive's docs for the full keyboard map.

Usage notes

App navigation column with sections, collapsible groups and an icon-only rail. For a top bar use Navbar.

<Sidebar collapsible header={<Brand />}>
  <SidebarSection title="Workspace">
    <SidebarItem href="/" icon={<HomeIcon />} isCurrent>Home</SidebarItem>
    <SidebarGroup icon={<FolderIcon />} title="Projects">
      <SidebarItem href="/p/web" icon={<FolderIcon />}>Website</SidebarItem>
    </SidebarGroup>
  </SidebarSection>
</Sidebar>
  • Mark the active item with isCurrent (sets aria-current="page"). Item children must be a string; it becomes the aria-label and tooltip when collapsed.
  • Collapse is uncontrolled by default (defaultCollapsed); control it with collapsed and onCollapsedChange. collapsible shows the toggle button.
  • In the collapsed rail, SidebarGroup shows its children flat, so they need their own icon.
  • variant is plain (default) or floating; header and footer are slots. The nav is labelled “Main”.
Example source · show
import { Avatar } from '../avatar'
import { Badge } from '../badge'
import { Sidebar, SidebarGroup, SidebarItem, SidebarSection } from './sidebar'

const I = ({ d }: { d: string }) => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <path d={d} />
  </svg>
)
const icons = {
  home: 'M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z',
  inbox: 'M22 12h-6l-2 3h-4l-2-3H2M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1',
  issues: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20M12 8v4M12 16h.01',
  folder: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z',
  chart: 'M3 3v18h18M7 16l4-4 4 4 5-6',
  settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-2.9 1.2V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-2.9-1.2l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.7 1.7 0 0 0 3 15H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.2-2.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.7 1.7 0 0 0 9 4.6V4a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 2.9 1.2l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1A1.7 1.7 0 0 0 20.4 11H21a2 2 0 1 1 0 4z',
}

const Brand = () => (
  <div className="flex items-center gap-2 font-semibold text-fg">
    <span className="grid size-6 place-items-center rounded-md bg-accent text-xs text-accent-fg">A</span>
    Acme
  </div>
)

const Nav = (props: { variant?: 'plain' | 'floating'; defaultCollapsed?: boolean }) => (
  <div className="flex h-[30rem] w-[44rem] overflow-hidden rounded-xl border border-border bg-bg">
    <Sidebar
      {...props}
      collapsible
      header={<Brand />}
      footer={
        <div className="flex items-center gap-2 px-1 text-sm">
          <Avatar size="xs" name="Maya Chen" />
          {!props.defaultCollapsed && <span className="truncate text-fg-muted">Maya Chen</span>}
        </div>
      }
    >
      <SidebarSection>
        <SidebarItem href="#" icon={<I d={icons.home} />} isCurrent>Home</SidebarItem>
        <SidebarItem href="#" icon={<I d={icons.inbox} />} badge={<Badge tone="accent">4</Badge>}>Inbox</SidebarItem>
        <SidebarItem href="#" icon={<I d={icons.issues} />}>My issues</SidebarItem>
      </SidebarSection>
      <SidebarSection title="Workspace">
        <SidebarGroup icon={<I d={icons.folder} />} title="Projects" defaultExpanded>
          <SidebarItem href="#" icon={<span className="size-2 rounded-full bg-success" />}>Website</SidebarItem>
          <SidebarItem href="#" icon={<span className="size-2 rounded-full bg-warning" />}>Mobile app</SidebarItem>
        </SidebarGroup>
        <SidebarItem href="#" icon={<I d={icons.chart} />}>Insights</SidebarItem>
        <SidebarItem href="#" icon={<I d={icons.settings} />}>Settings</SidebarItem>
      </SidebarSection>
    </Sidebar>
    <div className="flex-1 p-6 text-sm text-fg-muted">Page content</div>
  </div>
)

export const styles = {
  Plain: () => <Nav />,
  Floating: () => <Nav variant="floating" />,
  Rail: () => <Nav defaultCollapsed />,
}

Source: packages/ui/src/sidebar/sidebar.tsx

Catalog

Category
navigation
Priority
Foundation
Primitive
composed
React Aria
NavigationTree