SidebarLayout

PreviousNext

A composable application layout with a responsive, collapsible sidebar and slot-based header areas

CM
Project Dashboard

Welcome to your dashboard

This example shows SidebarLayout with composable header slots.

'use client';

import { Avatar } from '@/ui/Avatar';
import { Dropdown } from '@/ui/Dropdown';
import { SidebarLayout } from '@/ui/SidebarLayout';
import { Button } from '@/ui/buttons/Button';

const items = [
  { label: 'Home', icon: 'Home' as const, href: '#' },
  {
    label: 'Projects',
    icon: 'HeartOutlined' as const,
    items: [
      { label: 'Project A', href: '#' },
      { label: 'Project B', href: '#' },
      { label: 'Project C', href: '#' },
    ],
  },
  { label: 'Team', icon: 'PeopleDouble' as const, href: '#' },
];

export function SidebarLayoutDemo() {
  const user = { firstName: 'John', lastName: 'Doe' };

  return (
    <SidebarLayout items={items}>
      <div data-slot="header-logo" className="text-white text-b3 font-semibold">
        CM
      </div>
      <div data-slot="header" className="text-white text-h6">
        Project Dashboard
      </div>
      <Dropdown data-slot="header-actions" aria-label="Open user actions">
        <span data-slot="label" className="hidden md:inline text-body">
          {user.firstName} {user.lastName}
        </span>
        <Avatar
          data-slot="label"
          alt={`${user.firstName} ${user.lastName}`}
          className="w-40 h-40"
          style={{ width: '40px', height: '40px' }}
        />
        <Button size="small">Logout</Button>
      </Dropdown>

      <div>
        <h1 className="text-h3 mb-16">Welcome to your dashboard</h1>
        <p className="text-body">This example shows SidebarLayout with composable header slots.</p>
      </div>
    </SidebarLayout>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/sidebar-layout.json

Usage

import { SidebarLayout } from '@/ui/SidebarLayout';
import { Dropdown } from '@/ui/Dropdown';
import { Avatar } from '@/ui/Avatar';
import { Button } from '@/ui/buttons/Button';
 
export function Component() {
  const user = { firstName: 'John', lastName: 'Doe' };
 
  return (
    <SidebarLayout
      items={[
        { label: 'Home', icon: 'Home', href: '#' },
        {
          label: 'Settings',
          icon: 'Compare',
          // Items with nested `items` render as buttons
          items: [
            { label: 'Profile', href: '#profile' },
            { label: 'Account', href: '#account' },
          ],
        },
        { label: 'Team', icon: 'PeopleDouble', href: '#team' },
      ]}
      activeIndex={0}
    >
      <img data-slot="header-logo" alt="Brand" src="/brand.svg" />
      <div data-slot="header">My Dashboard</div>
      <Dropdown data-slot="header-actions">
        <span data-slot="label" className="hidden md:inline text-body">
          {user.firstName} {user.lastName}
        </span>
        <Avatar
          data-slot="label"
          alt={`${user.firstName} ${user.lastName}`}
          className="w-40 h-40 cursor-pointer hover:opacity-80 transition-opacity"
          style={{ width: '40px', height: '40px' }}
        />
        <Button>Logout</Button>
      </Dropdown>
 
      <div>
        <h1>Welcome to your Dashboard</h1>
        <p>Your main content goes here.</p>
      </div>
    </SidebarLayout>
  );
}

API Reference

SidebarLayout Props

PropTypeDefaultDescription
itemsSidebarItem[]-Array of navigation items displayed in the sidebar (required)
childrenReactNode-Main content and optional slotted header content (required)
activeIndexnumber0Index of the active sidebar item used by the indicator
classNamestring-Additional CSS classes applied to the root container

SidebarItem Structure

SidebarItem is a typed union:

  • Link item: anchor props + required href
  • Button item: button props (no href)
PropertyTypeRequired forDescription
labelstringBothDisplay text for the navigation item
iconIconicNamesBothIcon name from the Trident Icons library
hrefstringLink itemsStandard anchor URL for direct navigation
itemsSubItem[]OptionalNested links (when present, parent item renders as button)

SubItem Structure

SubItem supports anchor attributes plus:

PropertyTypeDescription
labelstringDisplay text for the sub-item
hrefstringURL for the sub-item navigation

Slots

Use data-slot on children passed to SidebarLayout:

SlotDescription
header-logoContent rendered at the start of the header
headerMain header content
header-actionsContent rendered on the right side of the header

Children without data-slot are rendered in the main content area.

Examples

With Custom Header

CM
Project Dashboard

Custom header example

This layout demonstrates custom `header-logo` and `header` slots.

'use client';

import { SidebarLayout } from '@/ui/SidebarLayout';

const items = [
  { label: 'Dashboard', icon: 'Home' as const, href: '#' },
  { label: 'Analytics', icon: 'CheckOutlined' as const, href: '#' },
];

export function SidebarLayoutWithHeaderDemo() {
  return (
    <SidebarLayout items={items}>
      <div data-slot="header-logo" className="text-white text-b3 font-semibold">
        CM
      </div>
      <div data-slot="header" className="text-white text-h6">
        Project Dashboard
      </div>

      <div>
        <h1 className="text-h3 mb-16">Custom header example</h1>
        <p className="text-body">
          This layout demonstrates custom `header-logo` and `header` slots.
        </p>
      </div>
    </SidebarLayout>
  );
}

Minimal Navigation

Minimal Layout

Minimal navigation

This layout demonstrates a compact SidebarLayout setup.

'use client';

import { SidebarLayout } from '@/ui/SidebarLayout';

const items = [
  { label: 'Home', icon: 'Home' as const, href: '#' },
  { label: 'Settings', icon: 'Compare' as const, href: '#' },
];

export function SidebarLayoutMinimalDemo() {
  return (
    <SidebarLayout items={items} activeIndex={1}>
      <div data-slot="header" className="text-white text-h6">
        Minimal Layout
      </div>

      <div>
        <h1 className="text-h3 mb-16">Minimal navigation</h1>
        <p className="text-body">This layout demonstrates a compact SidebarLayout setup.</p>
      </div>
    </SidebarLayout>
  );
}

Notes

Responsive Behavior

  • Desktop: Features a collapse button with animated indicator that follows the active navigation item using CSS anchor positioning
  • Mobile: Replaces collapse button with a hamburger menu that slides the sidebar in from the left
  • The sidebar automatically collapses on mobile and can be toggled open with the hamburger icon
  • Animations use CSS transitions for smooth state changes
  • Navigation items with a href property render as anchor links for direct navigation
  • Items with nested items render as buttons and display a collapsible list of sub-navigation links
  • The active navigation indicator uses CSS anchor positioning to smoothly track the currently active item
  • Sub-items are automatically hidden when the sidebar is collapsed and marked as inert for accessibility

Header Composition

  • Header content is fully composable through slots (header-logo, header, header-actions)
  • A common pattern is to render Dropdown with slotted labels and action buttons inside header-actions
  • This keeps identity/logout logic outside SidebarLayout and makes the layout reusable

Accessibility

  • Full keyboard navigation support with visible focus indicators
  • ARIA labels for all interactive elements (expand/collapse, open/close menu)
  • Uses inert attribute to prevent interaction with collapsed sub-items
  • Semantic HTML with proper landmark regions (navigation, complementary, main)
  • Screen reader friendly with descriptive labels that update based on state

Styling Customization

  • Use the className prop to add custom Tailwind classes to the root container
  • The component uses Tailwind utility classes extensively for responsive design
  • For deeper customization, copy the component source and modify the internal classes

Performance

  • Uses useState for local state management (collapse state)
  • Callbacks are memoized with useCallback to prevent unnecessary re-renders
  • CSS transitions handle all animations without JavaScript animation frames