Sidebar
BetaNavegación lateral de una app — grupos de ítems con icono, que se pliega a un riel de iconos.
registry/ds/sidebar.tsx"use client";
import { Avatar } from "@/registry/ds/avatar";
import { BarChart, Bell, BookOpen, Calendar, HelpCircle, Home, LogOut, Settings } from "@/registry/ds/icons";
import { Menu, MenuItem, MenuSeparator } from "@/registry/ds/menu";
import { Sidebar, SidebarFooter, SidebarGroup, SidebarHeader, SidebarItem, SidebarNav, useSidebar } from "@/registry/ds/sidebar";
const Logo = () => (
<svg viewBox="0 0 24 24" aria-hidden>
<rect width="24" height="24" rx="6" fill="var(--primary)" />
<path d="M7 12.5l3.5 3.5L17 9" stroke="var(--primary-fg)" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
</svg>
);
/** El footer se adapta: plegado queda solo el avatar */
function UserMenu() {
const { collapsed } = useSidebar();
return (
<Menu
side="top"
align="start"
trigger={
<button
type="button"
aria-label="Cuenta de Julia Arce"
className={`flex w-full items-center gap-3 rounded-md p-1.5 text-left transition-colors hover:bg-bg-subtle focus-visible:shadow-[inset_0_0_0_2px_var(--focus-ring)] focus-visible:outline-none ${collapsed ? "justify-center" : ""}`}
>
<Avatar name="Julia Arce" size="sm" />
{!collapsed && (
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-medium text-fg">Julia Arce</span>
<span className="block truncate text-xs text-fg-muted">julia@campus.edu</span>
</span>
)}
</button>
}
>
<MenuItem icon={<Settings />}>Configuración</MenuItem>
<MenuSeparator />
<MenuItem icon={<LogOut />}>Cerrar sesión</MenuItem>
</Menu>
);
}
export default function SidebarBasic() {
return (
<div className="flex h-120 w-full overflow-hidden rounded-lg bg-bg-subtle">
<Sidebar collapsible>
<SidebarHeader logo={<Logo />} href="#">
Campus
</SidebarHeader>
<SidebarNav>
<SidebarItem href="#" icon={<Home />} active>
Inicio
</SidebarItem>
<SidebarItem href="#" icon={<Bell />} badge="3">
Notificaciones
</SidebarItem>
<SidebarGroup label="Académico">
<SidebarItem
icon={<BookOpen />}
items={[
{ label: "Plan de estudios", href: "#" },
{ label: "Correlativas", href: "#" },
{ label: "Inscripción", href: "#" },
]}
>
Materias
</SidebarItem>
<SidebarItem href="#" icon={<Calendar />}>
Calendario
</SidebarItem>
<SidebarItem href="#" icon={<BarChart />}>
Notas
</SidebarItem>
</SidebarGroup>
<SidebarGroup label="Cuenta">
<SidebarItem href="#" icon={<Settings />}>
Configuración
</SidebarItem>
<SidebarItem href="#" icon={<HelpCircle />}>
Ayuda
</SidebarItem>
</SidebarGroup>
</SidebarNav>
<SidebarFooter>
<UserMenu />
</SidebarFooter>
</Sidebar>
<div className="flex-1 space-y-4 p-6" aria-hidden>
<div className="h-6 w-40 rounded-md bg-surface" />
<div className="grid gap-4 sm:grid-cols-3">
{[0, 1, 2].map((i) => (
<div key={i} className="h-24 rounded-lg border border-border bg-bg-elevated" />
))}
</div>
<div className="h-48 rounded-lg border border-border bg-bg-elevated" />
</div>
</div>
);
}Cuándo usarlo
Apps con más de 5 secciones o con secciones que tienen subpáginas: campus, paneles de gestión, back-office. Un sitio público con 4 destinos no necesita sidebar; usa el Navbar.
- Orden por frecuencia, no alfabético: lo que se usa a diario arriba. Inicio siempre primero.
- Grupos de 2 a 6 ítems con un título corto. Dos grupos como mínimo para que valga la pena; si no, lista plana.
- Un nivel de anidado y no más. Un ítem con sub-ítems agrupa páginas hermanas ("Materias → Plan, Correlativas, Inscripción"); si el sub-ítem tiene a su vez hijos, es una sección aparte con sus propias Tabs.
- Un solo ítem activo. Si está en un grupo anidado, el grupo arranca abierto y el padre se ve en text/default.
- El
badgees un número o un Badge corto: cosas pendientes, no decoración. Plegado se vuelve un punto. - Abajo va lo que no es navegación: usuario con su Menu, ayuda, salir.
Plegado
Con collapsible, un botón abajo lo lleva a un riel de 64 px: quedan los
iconos, cada uno con su nombre en un Tooltip. Es
para pantallas medianas o para gente que ya conoce la app y quiere más lugar.
Un ítem con sub-ítems no se puede abrir plegado: al hacer clic, despliega el
sidebar con el grupo abierto.
import { BarChart, BookOpen, Calendar, Home, Settings } from "@/registry/ds/icons";
import { Sidebar, SidebarGroup, SidebarHeader, SidebarItem, SidebarNav } from "@/registry/ds/sidebar";
const Logo = () => (
<svg viewBox="0 0 24 24" aria-hidden>
<rect width="24" height="24" rx="6" fill="var(--primary)" />
<path d="M7 12.5l3.5 3.5L17 9" stroke="var(--primary-fg)" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
</svg>
);
/** Riel de iconos: pasá el mouse para ver los nombres; el ítem con sub-ítems despliega el sidebar */
export default function SidebarCollapsed() {
return (
<div className="flex h-96 w-full overflow-hidden rounded-lg">
<Sidebar defaultCollapsed collapsible variant="subtle">
<SidebarHeader logo={<Logo />} href="#">
Campus
</SidebarHeader>
<SidebarNav>
<SidebarItem href="#" icon={<Home />}>
Inicio
</SidebarItem>
<SidebarGroup label="Académico">
<SidebarItem icon={<BookOpen />} items={[{ label: "Plan de estudios", href: "#" }, { label: "Correlativas", href: "#", active: true }]}>
Materias
</SidebarItem>
<SidebarItem href="#" icon={<Calendar />} badge="2">
Calendario
</SidebarItem>
<SidebarItem href="#" icon={<BarChart />}>
Notas
</SidebarItem>
</SidebarGroup>
<SidebarGroup label="Cuenta">
<SidebarItem href="#" icon={<Settings />}>
Configuración
</SidebarItem>
</SidebarGroup>
</SidebarNav>
</Sidebar>
<div className="flex-1 p-6 text-sm text-fg-muted">Correlativas de Cálculo II</div>
</div>
);
}En mobile
No se pliega: la misma navegación va adentro de un Drawer que abre el ☰ del
Navbar. Los SidebarItem y SidebarGroup funcionan igual ahí adentro. Ver
estructura de app.
| Desplegado | Plegado | |
|---|---|---|
| Ancho | 256 | 64 |
| Header | 56 de alto, padding 12, logo 24 + nombre Text md/Semibold | logo centrado |
| Ítem | 36 de alto, padding 12, gap 12, icono 20, Text sm/Medium, radius-md | 40 × 40, icono centrado |
| Sub-ítem | 32 de alto, Text sm, indentado 22 + línea + 10 | — |
| Grupo | título Text xs/Medium mayúsculas, padding 12, 16 arriba | línea de 24 × 1 |
| Badge | Text xs a la derecha | punto 8 primary/solid arriba a la derecha |
| Footer | padding 8, borde arriba | igual, contenido centrado |
Padding del nav: 8. Gap entre ítems: 2. Transición de ancho: Motion/fast, ease-move.
| Parte | Propiedad | Token |
|---|---|---|
| Panel default | Fondo + borde derecho | bg/default + border/default |
| Panel subtle | Fondo | bg/subtle |
| Ítem | Texto + icono | text/muted + icon/muted |
| Ítem hover | Fondo + texto + icono | bg/subtle + text/default + icon/default |
| Ítem activo | Fondo + texto + icono | primary/faint + primary/text + primary/accent |
| Padre con hijo activo | Texto | text/default |
| Sub-ítem | Texto · línea | text/muted · border/default |
| Sub-ítem activo | Fondo + texto | primary/faint + primary/text |
| Título de grupo | Texto | text/subtle |
| Chevron | Icono | icon/subtle |
| Badge | Texto · punto | text/subtle · primary/solid |
| Foco | Anillo interior 2 px | focus/ring |
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/sidebar.jsonTrae el Tooltip para el modo plegado. En un
flex horizontal se estira solo hasta el alto de la fila; en una página larga
conviene fijarlo: className="sticky top-14 h-[calc(100dvh-3.5rem)] self-start"
(como en los templates).
<Sidebar collapsible>
<SidebarHeader logo={<Logo />} href="/">Campus</SidebarHeader>
<SidebarNav>
<SidebarItem href="/" icon={<Home />} active>Inicio</SidebarItem>
<SidebarGroup label="Académico">
<SidebarItem icon={<BookOpen />} items={[{ label: "Plan", href: "/plan" }]}>Materias</SidebarItem>
</SidebarGroup>
</SidebarNav>
<SidebarFooter>…</SidebarFooter>
</Sidebar>Playground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
<Sidebar collapsible>
<SidebarHeader logo={<Logo />} href="/">
Campus
</SidebarHeader>
<SidebarNav>
<SidebarItem href="/" icon={<Home />}>Inicio</SidebarItem>
<SidebarGroup label="Académico">
<SidebarItem icon={<BookOpen />} items={[{ label: "Plan de estudios", href: "/plan" }, { label: "Correlativas", href: "/correlativas", active: true }]}>Materias</SidebarItem>
<SidebarItem href="/calendario" icon={<Calendar />} badge="2">Calendario</SidebarItem>
<SidebarItem href="/notas" icon={<BarChart />}>Notas</SidebarItem>
</SidebarGroup>
<SidebarGroup label="Cuenta">
<SidebarItem href="/configuracion" icon={<Settings />}>Configuración</SidebarItem>
</SidebarGroup>
</SidebarNav>
<SidebarFooter>
<UserMenu />
</SidebarFooter>
</Sidebar>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
collapsible | boolean | false | Muestra abajo el botón para plegar a riel de iconos. |
collapsed | boolean | — | Plegado controlado. Sin esto, maneja su propio estado. |
defaultCollapsed | boolean | false | Estado inicial cuando no es controlado. |
onCollapsedChange | (collapsed: boolean) => void | — | Para guardar la preferencia (localStorage, cookie). |
variant | "default" | "subtle" | "default" | bg/default o bg/subtle. |
Partes
| Componente | Props | Qué es |
|---|---|---|
SidebarHeader | logo, href | Marca arriba. Plegado queda el logo. |
SidebarNav | label (default "Principal") | El <nav> con la lista. |
SidebarGroup | label | Sección con título. |
SidebarItem | href, icon, active, badge, onClick, items, defaultOpen | Un destino, o un grupo anidado si tiene items. Sin href es un botón. |
SidebarFooter | — | Lo de abajo (usuario, ayuda). |
SidebarToggle | — | El botón plegar / desplegar, por si lo querés en otro lugar. |
useSidebar() | → { collapsed, setCollapsed } | Para adaptar lo que pongas en header o footer. |
items es un array de { label, href?, active?, onClick? }. Con Next.js,
active={pathname === href} y onClick={() => router.push(href)} si
preferís botones.
<aside>con un<nav aria-label>adentro; la lista es un<ul>real y cada grupo una lista anidada nombrada por su título. El lector anuncia "Académico, lista, 3 elementos".- El ítem actual lleva
aria-current="page". - Un ítem con sub-ítems es un botón con
aria-expanded; el panel abre y cierra con Enter o Espacio. - Plegado, cada ítem conserva su nombre (texto solo para lectores) y lo
muestra en un Tooltip al pasar o enfocar. El botón plegar dice "Plegar menú"
/ "Desplegar menú" y expone
aria-expanded. - Estado activo no solo por color: fondo + peso del texto + icono en primary.
| Tecla | Qué hace |
|---|---|
| Tab | Recorre los ítems en orden. |
| EnterEspacio | Activa el link o abre / cierra el grupo. |
| Esc | Cierra el Tooltip de un ítem plegado. |