Sidebar
A flexible and responsive sidebar component with animations, collapsible behavior, and comprehensive customization options.
Advanced Dashboard
A comprehensive workspace with team management and analytics.
Projects
12
+2 this week
Tasks
48
12 pending
Team
8
members
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarText,
} from "@/components/ui/Sidebar";
import { Home, Search, FileText, Settings } from "lucide-react";
function SidebarExample() {
const [collapsed, setCollapsed] = useState(false);
return (
<div className="relative h-96 w-full overflow-hidden rounded-[var(--radius)] border">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
<SidebarHeader>
<div className="h-8 w-8 rounded bg-primary">A</div>
<SidebarText>App Name</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="home" label="Home" icon={Home} />
<SidebarItem id="search" label="Search" icon={Search} />
<SidebarItem id="documents" label="Documents" icon={FileText} />
<SidebarItem id="settings" label="Settings" icon={Settings} />
</SidebarBody>
</Sidebar>
<SidebarContent sidebarCollapsed={collapsed} position="relative" className="p-6">
{/* Your main content here */}
</SidebarContent>
</div>
);
}
Installation
Install following dependencies:
npm install motion class-variance-authority lucide-react
pnpm add motion class-variance-authority lucide-react
yarn add motion class-variance-authority lucide-react
bun add motion class-variance-authority lucide-react
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import { motion, AnimatePresence } from "motion/react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight, type LucideIcon } from "lucide-react";
import { Button } from "../button";
import { ScrollArea } from "../ScrollArea";
import { Separator } from "../separator";
const sidebarVariants = cva(
"z-40 bg-[hsl(var(--hu-background))] border-r border-[hsl(var(--hu-border))] flex flex-col",
{
variants: {
variant: {
default: "bg-[hsl(var(--hu-background))]",
elevated: "bg-[hsl(var(--hu-card))] shadow-lg",
ghost: "bg-[hsl(var(--hu-background))]/95 backdrop-blur-sm",
},
size: {
sm: "w-12",
default: "w-64",
lg: "w-72",
xl: "w-80",
},
position: {
fixed: "fixed top-0 left-0 h-screen",
relative: "relative h-full",
},
},
defaultVariants: {
variant: "default",
size: "default",
position: "fixed",
},
},
);
const sidebarHeaderVariants = cva(
"flex items-center border-b border-[hsl(var(--hu-border))] min-h-[3.5rem]",
{
variants: {
collapsed: {
true: "justify-center px-2",
false: "justify-between px-4",
},
},
defaultVariants: {
collapsed: false,
},
},
);
const sidebarItemVariants = cva(
"relative flex items-center rounded-[var(--radius)] text-sm font-medium cursor-pointer group",
{
variants: {
variant: {
default:
"text-[hsl(var(--hu-muted-foreground))] hover:text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))]",
active:
"text-[hsl(var(--hu-primary-foreground))] bg-[hsl(var(--hu-primary))] hover:bg-[hsl(var(--hu-primary))]/90",
ghost:
"text-[hsl(var(--hu-muted-foreground))] hover:text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))]/50",
},
collapsed: {
true: "w-10 h-10 justify-center px-0 py-0",
false: "px-3 py-2.5",
},
},
defaultVariants: {
variant: "default",
collapsed: false,
},
},
);
export interface SidebarProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof sidebarVariants> {
collapsed?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
collapsible?: boolean;
overlay?: boolean;
onOverlayClick?: () => void;
position?: "fixed" | "relative";
children: React.ReactNode;
}
const SidebarContext = React.createContext<{
collapsed: boolean;
activeItem?: string;
onItemClick?: (item: { id: string; label: string; href?: string }) => void;
sidebarId: string;
}>({
collapsed: false,
sidebarId: "",
});
const useSidebar = () => {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error("useSidebar must be used within a Sidebar");
}
return context;
};
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
(
{
className,
variant = "default",
size = "default",
position = "fixed",
collapsed: controlledCollapsed,
onCollapsedChange,
collapsible = true,
overlay = false,
onOverlayClick,
children,
...props
},
ref,
) => {
const [internalCollapsed, setInternalCollapsed] = React.useState(false);
const [activeItem, setActiveItem] = React.useState<string>();
const sidebarRef = React.useRef<HTMLDivElement>(null);
const sidebarId = React.useId();
const collapsed = controlledCollapsed ?? internalCollapsed;
React.useImperativeHandle(ref, () => sidebarRef.current!);
const handleToggleCollapse = () => {
const newCollapsed = !collapsed;
if (onCollapsedChange) {
onCollapsedChange(newCollapsed);
} else {
setInternalCollapsed(newCollapsed);
}
};
const handleItemClick = (item: {
id: string;
label: string;
href?: string;
}) => {
setActiveItem(item.id);
};
// Auto-collapse on mobile
React.useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768 && !collapsed) {
handleToggleCollapse();
}
};
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, [collapsed]);
// Keyboard navigation support
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape" && overlay && !collapsed) {
onOverlayClick?.();
}
};
if (overlay) {
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}
}, [overlay, collapsed, onOverlayClick]);
// Focus management for better keyboard navigation accessibility
const focusableElementsRef = React.useRef<HTMLElement[]>([]);
React.useEffect(() => {
// Update focusable elements when sidebar state changes
const updateFocusableElements = () => {
if (sidebarRef.current) {
const elements = sidebarRef.current.querySelectorAll(
'button, a, [tabindex]:not([tabindex="-1"])',
) as NodeListOf<HTMLElement>;
focusableElementsRef.current = Array.from(elements);
}
};
updateFocusableElements();
// Re-scan when collapsed state changes
const timeoutId = setTimeout(updateFocusableElements, 300);
return () => clearTimeout(timeoutId);
}, [collapsed]);
// Extract header, body and footer from children
const headerChild = React.Children.toArray(children).find(
(child) => React.isValidElement(child) && child.type === SidebarHeader,
);
const bodyChild = React.Children.toArray(children).find(
(child) => React.isValidElement(child) && child.type === SidebarBody,
);
const footerChild = React.Children.toArray(children).find(
(child) => React.isValidElement(child) && child.type === SidebarFooter,
);
const sidebarContent = (
<SidebarContext.Provider
value={{
collapsed,
activeItem,
onItemClick: handleItemClick,
sidebarId,
}}
>
{" "}
<motion.aside
ref={(node) => {
const divNode = node as HTMLDivElement | null;
if (typeof ref === "function") {
ref(divNode);
} else if (ref) {
ref.current = divNode;
}
sidebarRef.current = divNode;
}}
className={cn(
sidebarVariants({
variant,
size: collapsed ? "sm" : size,
position,
}),
className,
)}
initial={false}
animate={{
width: collapsed
? 57
: size === "lg"
? 288
: size === "xl"
? 320
: 256,
}}
transition={{
duration: 0.25,
ease: [0.4, 0, 0.2, 1],
}}
style={props.style}
id={props.id || sidebarId}
role="complementary"
aria-label={
collapsed ? "Collapsed navigation sidebar" : "Navigation sidebar"
}
aria-expanded={!collapsed}
aria-hidden={overlay && collapsed}
>
{/* Header */}
{(headerChild || collapsible) && (
<div className={cn(sidebarHeaderVariants({ collapsed }))}>
{collapsed ? (
// Show only toggle button when collapsed
collapsible && (
<Button
variant="ghost"
size="icon"
onClick={handleToggleCollapse}
className="h-8 w-8 shrink-0"
aria-label="Expand sidebar"
aria-controls={sidebarId}
aria-expanded={false}
>
<ChevronRight size={16} />
</Button>
)
) : (
// Show header content and toggle button when expanded
<>
<AnimatePresence mode="wait">
{headerChild && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="flex-1"
>
{headerChild}
</motion.div>
)}
</AnimatePresence>
{collapsible && (
<Button
variant="ghost"
size="icon"
onClick={handleToggleCollapse}
className="h-8 w-8 shrink-0"
aria-label="Collapse sidebar"
aria-controls={sidebarId}
aria-expanded={true}
>
<ChevronLeft size={16} />
</Button>
)}
</>
)}
</div>
)}
{/* Body */}
{bodyChild}
{/* Footer */}
{footerChild && (
<div
className={cn(
"border-t border-[hsl(var(--hu-border))]",
collapsed ? "p-2" : "p-3",
)}
>
{footerChild}
</div>
)}
</motion.aside>
</SidebarContext.Provider>
);
if (overlay) {
return (
<>
{/* Overlay */}
<AnimatePresence>
{!collapsed && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 z-30 bg-black/50 md:hidden"
onClick={onOverlayClick}
role="presentation"
aria-hidden="true"
/>
)}
</AnimatePresence>
{sidebarContent}
</>
);
}
return sidebarContent;
},
);
Sidebar.displayName = "Sidebar";
// SidebarBody component
interface SidebarBodyProps {
children: React.ReactNode;
className?: string;
}
const SidebarBody: React.FC<SidebarBodyProps> = ({ children, className }) => {
const { collapsed } = useSidebar();
return (
<ScrollArea
className={cn("flex-1 py-2", collapsed ? "px-2" : "px-2", className)}
>
<nav role="navigation" aria-label="Main navigation">
<ul className="space-y-1 list-none" role="list">
{children}
</ul>
</nav>
</ScrollArea>
);
};
// SidebarItem component
interface SidebarItemProps {
id: string;
label: string;
icon?: LucideIcon;
href?: string;
badge?: React.ReactNode;
children?: React.ReactNode;
level?: number;
onClick?: () => void;
className?: string;
}
const SidebarItem: React.FC<SidebarItemProps> = ({
id,
label,
icon: Icon,
href,
badge,
children,
level = 0,
onClick,
className,
}) => {
const { collapsed, activeItem, onItemClick } = useSidebar();
const [expanded, setExpanded] = React.useState(false);
const isActive = activeItem === id;
const hasChildren = React.Children.count(children) > 0;
const itemId = React.useId();
const handleClick = () => {
if (hasChildren && !collapsed) {
setExpanded(!expanded);
}
onItemClick?.({ id, label, href });
onClick?.();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleClick();
} else if (
e.key === "ArrowRight" &&
hasChildren &&
!expanded &&
!collapsed
) {
e.preventDefault();
setExpanded(true);
} else if (e.key === "ArrowLeft" && hasChildren && expanded && !collapsed) {
e.preventDefault();
setExpanded(false);
}
};
const ItemContent = (
<>
{/* Static icon positioned always in the center-left area */}
<div
className={cn(
"flex items-center justify-center shrink-0",
collapsed ? "w-10 h-10" : "w-4 h-4 ml-0",
)}
aria-hidden="true"
>
{Icon && <Icon size={16} />}
</div>
{/* Text - simple conditional rendering, no animation */}
{!collapsed && <span className="ml-3 truncate flex-1">{label}</span>}
{/* Badge and chevron */}
{!collapsed && (badge || hasChildren) && (
<div className="flex items-center gap-2 ml-2">
{badge}
{hasChildren && (
<ChevronRight
size={14}
className={cn(
"shrink-0 transition-transform duration-200",
expanded && "rotate-90",
)}
aria-hidden="true"
/>
)}
</div>
)}
{/* Tooltip for collapsed state */}
{collapsed && (
<div
className="absolute left-full ml-2 px-2 py-1 bg-[hsl(var(--hu-popover))] text-[hsl(var(--hu-popover-foreground))] text-xs rounded-md border border-[hsl(var(--hu-border))] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 whitespace-nowrap z-50"
role="tooltip"
id={`${itemId}-tooltip`}
>
{label}
</div>
)}
</>
);
return (
<li role="none">
{href ? (
<a
href={href}
className={cn(
sidebarItemVariants({
variant: isActive ? "active" : "default",
collapsed,
}),
level > 0 &&
!collapsed &&
"ml-0 border-[hsl(var(--hu-border))] pl-3 relative before:absolute before:left-[-2px] before:top-1/2 before:w-3 before:h-[1px] before:bg-[hsl(var(--hu-border))] before:-translate-y-1/2",
"group relative no-underline",
className,
)}
onClick={onClick}
role="menuitem"
aria-current={isActive ? "page" : undefined}
aria-describedby={collapsed ? `${itemId}-tooltip` : undefined}
>
{ItemContent}
</a>
) : (
<button
type="button"
className={cn(
sidebarItemVariants({
variant: isActive ? "active" : "default",
collapsed,
}),
level > 0 &&
!collapsed &&
"ml-0 border-[hsl(var(--hu-border))] pl-3 relative before:absolute before:left-[-2px] before:top-1/2 before:w-3 before:h-[1px] before:bg-[hsl(var(--hu-border))] before:-translate-y-1/2",
"group relative w-full text-left border-none",
!isActive && "bg-transparent",
className,
)}
onClick={handleClick}
onKeyDown={handleKeyDown}
role="menuitem"
aria-expanded={hasChildren ? expanded : undefined}
aria-haspopup={hasChildren ? "menu" : undefined}
aria-current={isActive ? "page" : undefined}
aria-describedby={collapsed ? `${itemId}-tooltip` : undefined}
>
{ItemContent}
</button>
)}
{/* Children */}
<AnimatePresence>
{hasChildren && expanded && !collapsed && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{
duration: 0.25,
ease: [0.4, 0, 0.2, 1],
}}
className="overflow-hidden"
>
<ul
className="space-y-1 py-1 ml-2 border-l border-[hsl(var(--hu-border))]/50 pl-2 list-none"
role="menu"
aria-label={`${label} submenu`}
>
{React.Children.map(children, (child) => {
if (React.isValidElement(child) && child.type === SidebarItem) {
return React.cloneElement(
child as React.ReactElement<SidebarItemProps>,
{
level: level + 1,
...(child.props as SidebarItemProps),
},
);
}
return child;
})}
</ul>
</motion.div>
)}
</AnimatePresence>
</li>
);
};
// Sidebar content wrapper for responsive behavior
export interface SidebarContentProps {
children: React.ReactNode;
sidebarCollapsed?: boolean;
className?: string;
position?: "fixed" | "relative";
}
const SidebarContent: React.FC<SidebarContentProps> = ({
children,
sidebarCollapsed = false,
className,
position = "fixed",
}) => {
if (position === "relative") {
return (
<main className={cn("flex-1", className)} role="main">
{children}
</main>
);
}
return (
<motion.main
className={cn("flex-1", className)}
initial={false}
animate={{
marginLeft: sidebarCollapsed ? 48 : 256,
}}
transition={{
duration: 0.25,
ease: [0.4, 0, 0.2, 1],
}}
role="main"
>
<div className="md:hidden">
<motion.div
animate={{
marginLeft: 0,
}}
>
{children}
</motion.div>
</div>
<div className="hidden md:block">{children}</div>
</motion.main>
);
};
// Utility components
const SidebarHeader: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
return (
<div className={cn("flex items-center gap-2", className)}>{children}</div>
);
};
const SidebarFooter: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
return <div className={cn("space-y-1", className)}>{children}</div>;
};
const SidebarSeparator: React.FC<{
className?: string;
children?: React.ReactNode;
}> = ({ className, children }) => {
const { collapsed } = useSidebar();
if (collapsed && children) {
// In collapsed mode, just show the separator without text
return (
<div className={cn("my-2", className)}>
<Separator className="mx-2" />
</div>
);
}
return (
<div className={cn("my-2", className)}>
<Separator className="mx-2">{children}</Separator>
</div>
);
};
const SidebarText: React.FC<{
children: React.ReactNode;
className?: string;
animation?: boolean;
}> = ({ children, className, animation = true }) => {
const { collapsed } = useSidebar();
if (!animation) {
return !collapsed ? <span className={className}>{children}</span> : null;
}
return (
<AnimatePresence mode="wait">
{!collapsed && (
<motion.span
initial={{ opacity: 0, width: 0 }}
animate={{ opacity: 1, width: "auto" }}
exit={{ opacity: 0, width: 0 }}
transition={{ duration: 0.15 }}
className={cn("truncate", className)}
>
{children}
</motion.span>
)}
</AnimatePresence>
);
};
export {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarFooter,
SidebarSeparator,
useSidebar,
sidebarVariants,
sidebarItemVariants,
SidebarText,
};
npx hextaui@latest add sidebar
pnpm dlx hextaui@latest add sidebar
yarn dlx hextaui@latest add sidebar
bun x hextaui@latest add sidebar
Usage Guide
Basic Setup
The Sidebar component uses a compositional API that allows you to build flexible sidebar layouts:
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarFooter,
} from "@/components/ui/Sidebar";
function Layout() {
const [collapsed, setCollapsed] = useState(false);
return (
<div className="flex h-screen">
<Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
<SidebarHeader>
<Logo />
<SidebarText>My App</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="home" label="Home" icon={Home} />
<SidebarItem id="settings" label="Settings" icon={Settings} />
</SidebarBody>
<SidebarFooter>
<UserProfile />
</SidebarFooter>
</Sidebar>
<SidebarContent sidebarCollapsed={collapsed}>
<YourMainContent />
</SidebarContent>
</div>
);
}
Advanced Features
Nested Navigation
<SidebarItem id="projects" label="Projects" icon={Folder}>
<SidebarItem id="active" label="Active Projects" />
<SidebarItem id="archived" label="Archived Projects" />
</SidebarItem>
With Badges and Actions
<SidebarItem
id="notifications"
label="Notifications"
icon={Bell}
badge={<Badge variant="destructive">3</Badge>}
onClick={() => handleNotificationClick()}
/>
Mobile Responsive
<Sidebar
overlay={true}
onOverlayClick={() => setCollapsed(true)}
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
{/* Sidebar content */}
</Sidebar>
Text Hiding Behavior
The sidebar automatically hides text content when collapsed. Use the SidebarText
component for text that should animate in and out:
// Text that animates with smooth transitions
<SidebarText className="font-medium">
Dashboard
</SidebarText>
// Text that appears/disappears instantly
<SidebarText animation={false}>
<ChevronDown size={14} />
</SidebarText>
Examples
Basic Sidebar
Welcome to HextaUI
This is the main content area. The sidebar will affect the width of this content.
Card 1
Some content here
Card 2
Some content here
"use client";
import * as React from "react";
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarText,
} from "./sidebar";
import { cn } from "@/lib/utils";
import { Home, Search, Settings, FileText } from "lucide-react";
export function SidebarBasic() {
const [collapsed, setCollapsed] = React.useState(false);
return (
<div className="relative h-screen w-full flex overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))]">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
<SidebarHeader>
<img
className="rounded-[var(--radius)] bg-[hsl(var(--hu-background))] border border-[hsl(var(--hu-border))] shrink-0"
src="https://api.dicebear.com/9.x/micah/svg?seed=Sara"
width={35}
height={35}
/>
<SidebarText className="font-semibold text-lg">HextaUI</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="home" label="Home" icon={Home} />
<SidebarItem id="search" label="Search" icon={Search} />
<SidebarItem id="documents" label="Documents" icon={FileText} />
<SidebarItem id="settings" label="Settings" icon={Settings} />
</SidebarBody>
</Sidebar>
<SidebarContent
sidebarCollapsed={collapsed}
position="relative"
className="p-6"
>
<div className="space-y-4">
<h1 className="text-2xl font-bold">Welcome to HextaUI</h1>
<p className="text-[hsl(var(--hu-muted-foreground))]">
This is the main content area. The sidebar will affect the width of this content.
</p>
</div>
</SidebarContent>
</div>
);
}
With Profile Dropdown
Dashboard
Welcome back! Here's your dashboard overview.
Projects
12
Tasks
24
"use client";
import * as React from "react";
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarFooter,
SidebarText,
} from "./sidebar";
import { Badge } from "../badge";
import { Button } from "../button";
import { Avatar, AvatarImage, AvatarFallback } from "../avatar";
import { cn } from "@/lib/utils";
import {
Home,
BarChart3,
Calendar,
Mail,
Folder,
ChevronDown,
LogOut,
User,
Settings,
Bell,
} from "lucide-react";
export function SidebarWithProfile() {
const [collapsed, setCollapsed] = React.useState(false);
return (
<div className="relative h-screen w-full flex overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))]">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
<SidebarHeader>
<img
className="rounded-[var(--radius)] bg-[hsl(var(--hu-background))] border border-[hsl(var(--hu-border))] shrink-0"
src="https://api.dicebear.com/9.x/micah/svg?seed=Sara"
width={35}
height={35}
/>
<SidebarText className="font-semibold text-lg">HextaUI</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="dashboard" label="Dashboard" icon={Home} />
<SidebarItem
id="projects"
label="Projects"
icon={Folder}
badge={
<Badge variant="secondary" size="sm">
12
</Badge>
}
/>
<SidebarItem id="analytics" label="Analytics" icon={BarChart3} />
<SidebarItem id="calendar" label="Calendar" icon={Calendar} />
<SidebarItem
id="mail"
label="Mail"
icon={Mail}
badge={
<Badge variant="destructive" size="sm">
3
</Badge>
}
/>
</SidebarBody>
<SidebarFooter>
{/* Profile dropdown implementation */}
</SidebarFooter>
</Sidebar>
</div>
);
}
With Team Selector
HextaUI Overview
Manage your team projects and settings.
"use client";
import * as React from "react";
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarText,
} from "./sidebar";
import { Button } from "../button";
import { cn } from "@/lib/utils";
import {
Home,
Folder,
Users,
BarChart3,
CheckCircle,
Archive,
FileText,
ChevronDown,
} from "lucide-react";
export function SidebarWithTeamSelector() {
const [collapsed, setCollapsed] = React.useState(false);
const [selectedTeam, setSelectedTeam] = React.useState("HextaUI");
const teams = [
{ id: "Personal", name: "Personal", role: "Owner" },
{ id: "HextaStudio", name: "HextaStudio", role: "Member" },
{ id: "HextaUI", name: "HextaUI", role: "Admin" },
];
return (
<div className="relative h-screen w-full flex overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))]">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
<SidebarHeader>
{/* Team selector dropdown */}
</SidebarHeader>
<SidebarBody>
<SidebarItem id="overview" label="Overview" icon={Home} />
<SidebarItem id="projects" label="Projects" icon={Folder}>
<SidebarItem id="active-projects" label="Active" icon={CheckCircle} />
<SidebarItem id="archived-projects" label="Archived" icon={Archive} />
<SidebarItem id="draft-projects" label="Drafts" icon={FileText} />
</SidebarItem>
<SidebarItem id="team" label="Team" icon={Users} />
<SidebarItem id="analytics" label="Analytics" icon={BarChart3} />
</SidebarBody>
</Sidebar>
</div>
);
}
Advanced Sidebar
Advanced Dashboard
A comprehensive workspace with team management and analytics.
Projects
12
+2 this week
Tasks
48
12 pending
Team
8
members
"use client";
import * as React from "react";
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarFooter,
SidebarSeparator,
SidebarText,
} from "./sidebar";
import { Badge } from "../badge";
import { Button } from "../button";
import { Avatar, AvatarImage, AvatarFallback } from "../avatar";
import { cn } from "@/lib/utils";
import {
Home,
Briefcase,
Folder,
CheckCircle,
FileText,
BarChart3,
Users,
Shield,
Settings,
HelpCircle,
ChevronDown,
User,
Bell,
Moon,
LogOut,
} from "lucide-react";
export function SidebarAdvanced() {
const [collapsed, setCollapsed] = React.useState(false);
const adminItems = [
{ id: "admin", label: "Admin", icon: Shield },
{ id: "settings", label: "Settings", icon: Settings },
{ id: "help", label: "Help & Support", icon: HelpCircle },
];
return (
<div className="relative h-screen w-full flex overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))]">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
>
<SidebarHeader>
<img
className="rounded-[var(--radius)] bg-[hsl(var(--hu-background))] border border-[hsl(var(--hu-border))] shrink-0"
src="https://api.dicebear.com/9.x/micah/svg?seed=Sara"
width={35}
height={35}
/>
<SidebarText className="font-semibold text-lg">HextaUI</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="dashboard" label="Dashboard" icon={Home} />
<SidebarItem id="workspace" label="Workspace" icon={Briefcase}>
<SidebarItem
id="projects"
label="Projects"
icon={Folder}
badge={
<Badge variant="secondary" size="sm">
8
</Badge>
}
/>
<SidebarItem
id="tasks"
label="Tasks"
icon={CheckCircle}
badge={
<Badge variant="destructive" size="sm">
3
</Badge>
}
/>
<SidebarItem id="documents" label="Documents" icon={FileText} />
</SidebarItem>
<SidebarItem id="analytics" label="Analytics" icon={BarChart3} />
<SidebarItem id="team" label="Team" icon={Users} />
</SidebarBody>
<SidebarFooter>
{/* Admin items and profile dropdown */}
</SidebarFooter>
</Sidebar>
</div>
);
}
Mobile Responsive
Mobile Layout
This layout is optimized for mobile devices with overlay sidebar.
"use client";
import * as React from "react";
import {
Sidebar,
SidebarBody,
SidebarItem,
SidebarContent,
SidebarHeader,
SidebarText,
} from "./sidebar";
import { Badge } from "../badge";
import { Button } from "../button";
import { Home, Search, Bell, User, Settings } from "lucide-react";
export function SidebarMobile() {
const [collapsed, setCollapsed] = React.useState(true);
return (
<div className="relative h-screen w-full flex overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))]">
<Sidebar
position="relative"
collapsed={collapsed}
onCollapsedChange={setCollapsed}
overlay={true}
onOverlayClick={() => setCollapsed(true)}
>
<SidebarHeader>
<img
className="rounded-[var(--radius)] bg-[hsl(var(--hu-background))] border border-[hsl(var(--hu-border))] shrink-0"
src="https://api.dicebear.com/9.x/micah/svg?seed=Sara"
width={35}
height={35}
/>
<SidebarText className="font-semibold text-lg">HextaUI</SidebarText>
</SidebarHeader>
<SidebarBody>
<SidebarItem id="home" label="Home" icon={Home} />
<SidebarItem id="search" label="Search" icon={Search} />
<SidebarItem
id="notifications"
label="Notifications"
icon={Bell}
badge={
<Badge variant="destructive" size="sm">
5
</Badge>
}
/>
<SidebarItem id="profile" label="Profile" icon={User} />
<SidebarItem id="settings" label="Settings" icon={Settings} />
</SidebarBody>
</Sidebar>
</div>
);
}
Props
Sidebar
Prop | Type | Default |
---|---|---|
children? | React.ReactNode | undefined |
overlay? | boolean | false |
position? | "fixed" | "relative" | "fixed" |
size? | "sm" | "default" | "lg" | "xl" | "default" |
variant? | "default" | "elevated" | "ghost" | "default" |
collapsible? | boolean | true |
onCollapsedChange? | (collapsed: boolean) => void | undefined |
collapsed? | boolean | false |
SidebarItem
Prop | Type | Default |
---|---|---|
onClick? | () => void | undefined |
children? | React.ReactNode | undefined |
badge? | React.ReactNode | undefined |
href? | string | undefined |
icon? | LucideIcon | undefined |
label? | string | undefined |
id? | string | undefined |
SidebarContent
Prop | Type | Default |
---|---|---|
className? | string | undefined |
children? | React.ReactNode | undefined |
position? | "fixed" | "relative" | "fixed" |
sidebarCollapsed? | boolean | false |
SidebarText
Prop | Type | Default |
---|---|---|
animation? | boolean | true |
className? | string | undefined |
children? | React.ReactNode | undefined |
Last updated on