Sidebar
Componente Sidebar do shadcn - navegação responsiva com seções recolhíveis, gaveta para dispositivos móveis e estado persistente.
Busque em todas as páginas da documentação
Componente Sidebar do shadcn - navegação responsiva com seções recolhíveis, gaveta para dispositivos móveis e estado persistente.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
npx shadcn@latest add sidebarimport {
Sidebar, SidebarContent, SidebarFooter, SidebarGroup,
SidebarGroupContent, SidebarGroupLabel, SidebarHeader,
SidebarMenu, SidebarMenuButton, SidebarMenuItem,
SidebarProvider, SidebarTrigger,
} from "@/components/ui/sidebar";
export function AppLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<h2 className="px-4 text-lg font-bold">Meu App</h2>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Principal</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">Dashboard</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/settings">Configurações</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<p className="px-4 text-xs text-muted-foreground">v1.0.0</p>
</SidebarFooter>
</Sidebar>
<main className="flex-1">
<SidebarTrigger className="m-4" />
{children}
</main>
</SidebarProvider>
);
}Quando usar isto: Quando seu aplicativo precisa de navegação persistente - painéis de controle, painéis administrativos, sites de documentação ou qualquer aplicação multi-página.
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
import {
Sidebar, SidebarContent, SidebarFooter, SidebarGroup,
SidebarGroupContent, SidebarGroupLabel, SidebarHeader,
SidebarMenu, SidebarMenuButton, SidebarMenuItem,
SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem,
SidebarProvider, SidebarTrigger, useSidebar,
} from "@/components/ui/sidebar";
import {
Collapsible, CollapsibleContent, CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
LayoutDashboard, Users, Settings, FileText,
ChevronDown, LogOut, ChevronsUpDown,
} from "lucide-react";
type NavItem = {
title: string;
href: string;
icon: React.ReactNode;
children?: { title: string; href: string }[];
};
const navItems: NavItem[] = [
{ title: "Dashboard", href: "/dashboard", icon: <LayoutDashboard className="h-4 w-4" /> },
{
title: "Usuários",
href: "/users",
icon: <Users className="h-4 w-4" />,
children: [
{ title: "Todos os Usuários", href: "/users" },
{ title: "Permissões", href: "/users/roles" },
{ title: "Convites", href: "/users/invitations" },
],
},
{
title: "Conteúdo",
href: "/content",
icon: <FileText className="h-4 w-4" />,
children: [
{ title: "Posts", href: "/content/posts" },
{ title: "Páginas", href: "/content/pages" },
{ title: "Mídia", href: "/content/media" },
],
},
{ title: "Configurações", href: "/settings", icon: <Settings className="h-4 w-4" /> },
];
function AppSidebar() {
const pathname = usePathname();
return (
<Sidebar collapsible="icon">
<SidebarHeader className="border-b px-4 py-3">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold">
A
</div>
<span className="font-semibold group-data-[collapsible=icon]:hidden">
Acme Inc
</span>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navegação</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{navItems.map((item) =>
item.children ? (
<Collapsible key={item.title} defaultOpen={pathname.startsWith(item.href)}>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton>
{item.icon}
<span>{item.title}</span>
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.children.map((child) => (
<SidebarMenuSubItem key={child.href}>
<SidebarMenuSubButton
asChild
isActive={pathname === child.href}
>
<Link href={child.href}>{child.title}</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
) : (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={pathname === item.href}>
<Link href={item.href}>
{item.icon}
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter className="border-t">
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="w-full">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs">
JD
</div>
<span className="group-data-[collapsible=icon]:hidden">John Doe</span>
<ChevronsUpDown className="ml-auto h-4 w-4 group-data-[collapsible=icon]:hidden" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start" className="w-48">
<DropdownMenuItem>Perfil</DropdownMenuItem>
<DropdownMenuItem>Cobrança</DropdownMenuItem>
<DropdownMenuItem>
<LogOut className="mr-2 h-4 w-4" /> Sair
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
);
}
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<main className="flex-1 overflow-auto">
<header className="flex items-center gap-4 border-b px-6 py-3">
<SidebarTrigger />
<h1 className="text-lg font-semibold">Dashboard</h1>
</header>
<div className="p-6">{children}</div>
</main>
</SidebarProvider>
);
}O que isto demonstra:
isActive para a página atualcollapsible="icon") - a sidebar encolhe para mostrar apenas íconesSidebarTriggerLink usando asChildSidebarProvider gerencia o estado aberto/fechado e o fornece via contextoSidebar renderiza um <aside> com largura fixa e comportamento responsivomd), a sidebar se torna uma gaveta deslizantecollapsible="icon" encolhe a sidebar para mostrar apenas ícones, ocultando rótulos de texto via group-data-[collapsible=icon]:hiddenuseSidebar() expõe state, open, setOpen, toggleSidebar e isMobileSidebarMenuButton lida com o estado ativo, tooltip em modo recolhido e navegação por tecladoEstado da sidebar controlado:
"use client";
import { useSidebar } from "@/components/ui/sidebar";
function SidebarToggle() {
const { open, setOpen, toggleSidebar, isMobile } = useSidebar();
return (
<button onClick={toggleSidebar}>
{open ? "Recolher" : "Expandir"}
</button>
);
}Sidebar com badges:
<SidebarMenuButton>
<Users className="h-4 w-4" />
<span>Usuários</span>
<span className="ml-auto rounded-full bg-primary px-2 py-0.5 text-xs text-primary-foreground">
12
</span>
</SidebarMenuButton>Sidebar com busca:
<SidebarHeader>
<div className="px-2 py-2">
<Input placeholder="Buscar..." className="h-8" />
</div>
</SidebarHeader>Sidebar do lado direito:
<SidebarProvider>
<main className="flex-1">{children}</main>
<Sidebar side="right" collapsible="none">
{/* Painel de propriedades, chat, etc. */}
</Sidebar>
</SidebarProvider>// Tipo de retorno do hook useSidebar
const sidebar: {
state: "expanded" | "collapsed";
open: boolean;
setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
} = useSidebar();
// Tipo do item de navegação
type NavItem = {
title: string;
href: string;
icon: React.ComponentType<{ className?: string }>;
badge?: number;
children?: Omit<NavItem, "icon" | "children">[];
};SidebarProvider deve envolver tanto a Sidebar quanto o conteúdo principal - O provedor gerencia o layout. Colocá-lo incorretamente quebra o contêiner flexível. Correção: Envolva no nível do layout.
Comportamento móvel vs desktop - No celular, a sidebar renderiza como uma gaveta sobreposta; no desktop, é inline. Se você renderizar condicionalmente com base em isMobile, certifique-se de lidar com a hidratação.
collapsible="icon" requer ícones - Se seus itens de menu não tiverem ícones, o modo recolhido mostrará espaço vazio. Correção: Sempre inclua um ícone em SidebarMenuButton.
Persistência baseada em cookie - A persistência de estado padrão usa cookies, o que requer um layout renderizado no servidor. Correção: Em SPAs, passe defaultOpen para SidebarProvider ou gerencie o estado você mesmo.
asChild em SidebarMenuButton - Ao usar Next.js <Link>, envolva-o com asChild para que o botão do menu delegue a renderização ao Link. Sem asChild, você obtém um botão envolvendo uma âncora (HTML inválido).
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Sidebar customizada | Você precisa de um comportamento não coberto pelo componente shadcn | A Sidebar do shadcn cobre suas necessidades |
| Navegação por abas | Aplicação mobile-first com abas inferiores | Painel desktop com muitos itens de navegação |
| Apenas barra de navegação superior | Sua aplicação tem menos de 6 páginas de nível superior | Você precisa de navegação aninhada e multi-nível |
| Gaveta (Sheet) | Você quer um painel deslizante temporário, não navegação persistente | Você precisa de navegação sempre visível |
SidebarProvider envolvendo tanto a Sidebar quanto o conteúdo mainSidebar com SidebarContent contendo SidebarGroup, SidebarMenu e SidebarMenuItemSidebarTrigger em algum lugar na área principal para alternar a sidebargroup-data-[collapsible=icon]:hidden<aside> com largura fixamd): renderiza como uma gaveta deslizante sobrepostaisMobile de useSidebar() para detectar o modo atual<Collapsible defaultOpen={pathname.startsWith(item.href)}>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton>{item.icon} {item.title}</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.children.map((child) => (
<SidebarMenuSubItem key={child.href}>
<SidebarMenuSubButton asChild isActive={pathname === child.href}>
<Link href={child.href}>{child.title}</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>state: "expanded" ou "collapsed"open / setOpen: estado booleano aberto e setteropenMobile / setOpenMobile: estado específico para mobileisMobile: se a viewport está abaixo do breakpoint mobiletoggleSidebar: alterna entre aberto e fechadomainmain sejam irmãosdefaultOpen para SidebarProvider ou gerencie o estado você mesmo<SidebarProvider>
<main className="flex-1">{children}</main>
<Sidebar side="right" collapsible="none">
{/* Painel de propriedades, chat, etc. */}
</Sidebar>
</SidebarProvider>asChild, SidebarMenuButton renderiza um <button> e seu <Link> renderiza um <a> dentro dele<button> envolvendo um <a> é HTML inválidoasChild quando o filho for um elemento <Link> ou <a>type NavItem = {
title: string;
href: string;
icon: React.ComponentType<{ className?: string }>;
badge?: number;
children?: Omit<NavItem, "icon" | "children">[];
};DropdownMenu dentro de SidebarFooter > SidebarMenu > SidebarMenuItemSidebarMenuButton como o DropdownMenuTrigger com asChildside="top" e align="start" em DropdownMenuContent para que ele abra para cimaRevisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥