HyperSaaS
FrontendUI Components

Sidebar

Application sidebar with workspace switcher, navigation, and user menu.

The dashboard sidebar is the primary navigation element, providing workspace switching, feature navigation, and user account access.

Structure

┌─────────────────────────┐
│ ◆ Workspace Name    ▼   │  ← WorkspaceSwitcherV2
│   Pro Plan              │
├─────────────────────────┤
│                         │
│ ▸ Workspace             │  ← NavMain (collapsible)
│     Home                │
│     Details             │
│     Members             │
│ ▸ Chats                 │
│     New Chat            │
│     History             │
│ ▸ Knowledge Bases       │
│     All KBs             │
│ ▸ Documents             │
│     All Documents       │
│                         │
├─────────────────────────┤
│ Dashboard               │  ← NavOther (links)
│ Workspaces              │
│ Settings                │
│ Billing                 │
├─────────────────────────┤
│ 👤 user@example.com  ▲  │  ← NavUser (dropdown)
└─────────────────────────┘

AppSidebar Component

The main sidebar wrapper receives workspace and user context from the layout:

interface AppSidebarProps {
  workspaces: Workspace[];
  user: UserDetails;
  currentWorkspaceId?: string;
}

It builds navigation items dynamically based on currentWorkspaceId:

const navMain = [
  {
    title: "Workspace",
    icon: Building2,
    items: [
      { title: "Home", url: `/dashboard/workspaces/${id}` },
      { title: "Details", url: `/dashboard/workspaces/${id}/details` },
    ],
  },
  {
    title: "Chats",
    icon: MessageSquare,
    items: [
      { title: "New Chat", url: `/dashboard/workspaces/${id}/chat` },
      { title: "History", url: `/dashboard/workspaces/${id}/chat/history` },
    ],
  },
  // ...
];

If no workspace is selected, navigation items are disabled.

Workspace Switcher

WorkspaceSwitcherV2 provides a dropdown to switch between workspaces:

  • Lists all user workspaces with icons
  • Shows current subscription status badge
  • Includes a "Create Workspace" button that opens a dialog
  • Navigates via router.push() on selection
function WorkspaceSwitcherV2({ workspaces, currentWorkspaceId }) {
  const router = useRouter();

  const handleSelect = (workspace: Workspace) => {
    router.push(`/dashboard/workspaces/${workspace.id}`);
  };

  // ...
}

Primary navigation with collapsible sections:

interface NavMainProps {
  items: {
    title: string;
    url: string;
    icon: LucideIcon;
    isActive?: boolean;
    items: { title: string; url: string }[];
  }[];
}

Each group expands/collapses with an animated chevron. Active state is determined by matching usePathname() against the item URLs.

User dropdown in the sidebar footer:

┌─────────────────────────┐
│ 👤 John Doe             │
│   john@example.com      │
├─────────────────────────┤
│ Profile                 │
│ Billing                 │
│ Workspaces              │
│ Settings                │
├─────────────────────────┤
│ Sign Out                │
└─────────────────────────┘

Sign Out calls the signOutAction() server action.

Mobile Sidebar

On mobile (< 768px), the sidebar renders as a Sheet (slide-out panel) instead of a persistent sidebar:

const isMobile = useIsMobile();

{isMobile ? (
  <Sheet>
    <SheetTrigger asChild>
      <Button variant="ghost" size="icon"><Menu /></Button>
    </SheetTrigger>
    <SheetContent side="left">
      <SidebarContent />
    </SheetContent>
  </Sheet>
) : (
  <aside className="w-64">
    <SidebarContent />
  </aside>
)}

The SidebarProvider context manages sidebar state (open/closed, mobile vs desktop):

<SidebarProvider>
  <AppSidebar {...props} />
  <SidebarInset>
    <header>
      <SidebarTrigger />
      <DynamicBreadcrumb />
    </header>
    <main>{children}</main>
  </SidebarInset>
</SidebarProvider>

The SidebarTrigger button toggles the sidebar on desktop (collapse) and mobile (sheet).

Dynamic Breadcrumb

The DynamicBreadcrumb component auto-generates breadcrumbs from the current route path:

Dashboard > Workspaces > My Workspace > Chats > Research Chat

Each segment is a clickable link except the last (current page).

On this page