Sidebar

La barra de navegación lateral izquierda colapsable para listar páginas y guías anidadas.

El componente Sidebar muestra la estructura de directorios del sitio, listando carpetas anidadas, rutas de páginas, encabezados de categorías y etiquetas de navegación.

La primitiva Sidebar es neutra de estilo: aporta estructura, accesibilidad (ARIA), comportamiento (restauración de scroll, autoapertura del grupo activo, scroll hasta el elemento activo) y un conjunto de atributos de estado data-*. Cada decisión visual pertenece a tu tema — colores, espaciados, bordes, tratamientos activo/hover. Estiliza los estados con variantes de Tailwind v4 (data-active:bg-primary-500/10) o CSS puro ([data-active] { ... }).

Ejemplo de Sidebar ComponibleLink

Construye una barra lateral personalizada responsiva usando los contenedores primitivos y el hook de enrutamiento automático:

tsx
// docs/components/CustomSidebar.tsx
import { Sidebar } from 'boltdocs/primitives'
import { useRoutes } from 'boltdocs/client'

export default function CustomSidebar() {
  const { routes } = useRoutes()

  return (
    <>
      {/* 1. Contenedor de escritorio */}
      <Sidebar className="hidden lg:flex flex-col w-sidebar sticky top-navbar h-[calc(100vh-var(--spacing-navbar))] border-r border-subtle bg-main">
        <Sidebar.Header className="px-4 py-3 flex items-center justify-between border-b border-subtle">
          <span className="text-xs font-bold uppercase tracking-wider text-muted">Navegación</span>
        </Sidebar.Header>
        <Sidebar.Content className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
          {/* Los elementos activos se exponen vía data-active; estilízalos en tu tema. */}
          <Sidebar.Items
            routes={routes}
            classNames={{
              item: 'rounded-md px-2.5 py-1.5 text-sm transition-colors text-muted hover:bg-surface hover:text-body data-active:bg-primary-500/10 data-active:text-primary-500 data-active:font-medium',
              groupHeader: 'px-2 mb-2 text-xs font-bold uppercase tracking-widest text-muted/50',
              groupContent: 'flex flex-col gap-0.5',
              subgroupContent: 'ml-4 pl-3 border-l border-subtle/50 mt-0.5 flex flex-col gap-0.5',
            }}
          />
        </Sidebar.Content>
      </Sidebar>

      {/* 2. Cajón móvil responsivo */}
      <Sidebar.Mobile
        overlayClassName="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm lg:hidden"
        className="fixed top-0 left-0 bottom-0 w-80 bg-main border-r border-subtle shadow-2xl outline-none"
      >
        <Sidebar.Header className="px-4 py-3 flex items-center justify-between border-b border-subtle">
          <span className="text-xs font-bold uppercase tracking-wider text-muted">Menú</span>
        </Sidebar.Header>
        <Sidebar.Content className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
          <Sidebar.Items routes={routes} />
        </Sidebar.Content>
      </Sidebar.Mobile>
    </>
  )
}

Referencia de Producción RealLink

A continuación se muestra una implementación de referencia completa y lista para producción que demuestra cómo construir un sistema de navegación lateral con todas las funciones (similar al tema integrado). Integra el logo, controles de cierre móviles, estructuras de escritorio vs. móvil y combina selectores de versión e idioma dentro del cajón móvil:

tsx
// docs/components/ProductionSidebar.tsx
import { Sidebar, Button, Menu } from 'boltdocs/primitives'
import { useRoutes, useNavbar, useUI, useVersion, useI18n } from 'boltdocs/client'
import { X, ChevronDown, Languages } from 'lucide-react'

export default function ProductionSidebar() {
  const { routes, config } = useRoutes()
  const { logo, title, logoProps } = useNavbar()
  const { closeSidebar } = useUI()

  const { currentVersionLabel, availableVersions, handleVersionChange } = useVersion()
  const { currentLocale, availableLocales, handleLocaleChange } = useI18n()

  const SidebarLogo = logo ? (
    <img
      src={logo}
      alt={logoProps?.alt || title}
      width={24}
      height={24}
      className="rounded-lg"
    />
  ) : null

  // 1. Selector de versión para móvil
  const MobileVersionSelector = availableVersions.length > 0 && (
    <select
      value={availableVersions.find(v => v.isCurrent)?.value}
      onChange={(e) => handleVersionChange(e.target.value)}
      className="flex-1 bg-surface border border-subtle rounded-lg px-3 py-2 text-xs font-semibold"
    >
      {availableVersions.map(v => (
        <option key={v.key} value={v.value}>{v.label}</option>
      ))}
    </select>
  )

  // 2. Selector de idioma para móvil
  const MobileLocaleSelector = availableLocales.length > 0 && (
    <select
      value={currentLocale}
      onChange={(e) => handleLocaleChange(e.target.value)}
      className="flex-1 bg-surface border border-subtle rounded-lg px-3 py-2 text-xs font-semibold"
    >
      {availableLocales.map(loc => (
        <option key={loc.key} value={loc.value}>{loc.label}</option>
      ))}
    </select>
  )

  const hasMobileSelectors = availableVersions.length > 0 || availableLocales.length > 0

  return (
    <>
      {/* A. Panel de escritorio */}
      <Sidebar className="hidden lg:flex flex-col w-sidebar sticky top-navbar h-[calc(100vh-var(--spacing-navbar))] border-r border-subtle bg-main">
        <Sidebar.Content className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
          <Sidebar.Items
            routes={routes}
            classNames={{
              item: 'rounded-md px-2.5 py-1.5 text-sm transition-colors text-muted hover:bg-surface hover:text-body data-active:bg-primary-500/10 data-active:text-primary-500',
            }}
          />
        </Sidebar.Content>
      </Sidebar>

      {/* B. Cajón móvil */}
      <Sidebar.Mobile
        overlayClassName="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm lg:hidden"
        className="fixed top-0 left-0 bottom-0 w-80 bg-main border-r border-subtle shadow-2xl outline-none"
      >
        <Sidebar.Header className="flex items-center justify-between border-b border-subtle px-4 py-3">
          <div className="flex items-center gap-3">
            {SidebarLogo}
            <span className="font-bold text-base text-body truncate max-w-[140px]">
              {title}
            </span>
          </div>

          <Button
            onPress={closeSidebar}
            className="h-8 w-8 flex items-center justify-center text-muted hover:text-body rounded-lg hover:bg-surface transition-colors"
          >
            <X size={18} />
          </Button>
        </Sidebar.Header>

        <Sidebar.Content className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
          {/* Selectores de versión/idioma dentro del cajón móvil */}
          {hasMobileSelectors && (
            <div className="flex flex-col gap-3 mb-6">
              <div className="flex gap-2">
                {MobileVersionSelector}
                {MobileLocaleSelector}
              </div>
              <div className="border-b border-subtle my-2" />
            </div>
          )}

          <Sidebar.Items routes={routes} />
        </Sidebar.Content>
      </Sidebar.Mobile>
    </>
  )
}

Atributos de Estado (data-*)Link

Las primitivas de Sidebar exponen su estado exclusivamente a través de atributos data-*. Solo están presentes cuando son verdaderos, por lo que el CSS basado en presencia funciona directamente. Decide tus propios colores (primary, secondary, success, ...) desde CSS — la primitiva nunca pinta nada por sí misma.

AtributoSe Emite EnPresente Cuando
data-activeenlace Sidebar.Link, contenedor Sidebar.GroupEl enlace / grupo contiene la ruta actual
data-openbotón de grupo colapsable + chevron, toggle de Sidebar.SubGroup + chevronLa sección está expandida
data-collapsiblecontenedor Sidebar.GroupEl grupo renderiza un botón de alternancia
data-depthenlace Sidebar.LinkProfundidad de anidado (número)
data-badgespan de insigniaTexto / tipo de la insignia (p. ej. new, updated, deprecated)
aria-currentenlace Sidebar.Link"page" cuando está activo
aria-expandedbotones de alternancia"true" / "false"

Estilizando el elemento activo desde tu tema — Tailwind v4:

tsx
<Sidebar.Items
  routes={routes}
  classNames={{
    item: 'rounded-md px-2.5 py-1.5 text-sm transition-colors text-muted hover:bg-soft hover:text-body data-active:bg-primary-500/10 data-active:text-primary-400',
  }}
/>

O CSS puro:

css
[data-sidebar-root] a[data-active] {
  color: var(--color-primary-400);
  background: color-mix(in srgb, var(--color-primary-500) 10%, transparent);
}

Los iconos renderizados dentro de un enlace heredan currentColor, por lo que colorear el enlace colorea el icono automáticamente — sin clases de estado por icono.


Subcomponentes ComponiblesLink

La primitiva Sidebar proporciona los siguientes subcomponentes para personalizar la estructura:

ComponenteEtiqueta HTMLDescripciónProps
Sidebar.Root / Sidebar<aside>Contenedor principal de escritorio de la barra lateral.ComponentBaseProps
Sidebar.Mobile<div> (Modal)Cajón móvil de la barra lateral con overlays accesibles.ComponentBaseProps
Sidebar.Header<div>Contenedor superior (ideal para logos, búsquedas o encabezados).ComponentBaseProps
Sidebar.Content<nav>Envoltorio de navegación con scroll que preserva las posiciones.ComponentBaseProps
Sidebar.Group<div>Agrupa enlaces relacionados bajo un título común.Sidebar.Group Props
Sidebar.Link<a>Enlace de navegación optimizado hacia una ruta.Sidebar.Link Props
Sidebar.SubGroup<div>Contenedor colapsable para sub-rutas anidadas.Sidebar.SubGroup Props
Sidebar.ItemVariesUtilidad de mapeo recursivo que renderiza enlaces o subgrupos automáticamente.Sidebar.Item Props
Sidebar.ItemsVariesContenedor de bucle automatizado que mapea y renderiza una colección de rutas.Sidebar.Items Props

Props de los ComponentesLink

ComponentBaseProps (Común)Link

PropiedadTipoDefaultDescripción
childrenReactNodeundefinedElementos hijos.
classNamestringundefinedClases CSS personalizadas.
styleCSSPropertiesundefinedEstilos en línea.

Props de Sidebar.MobileLink

PropiedadTipoDefaultDescripción
overlayClassNamestringundefinedClases del overlay a pantalla completa tras el cajón.

Props de Sidebar.GroupLink

PropiedadTipoDefaultDescripción
titlestringundefinedTexto del título de categoría.
iconElementTypeundefinedComponente de icono inicial.
childrenReactNodeRequeridoElementos hijos de la barra lateral.
collapsiblebooleanfalseRenderiza el encabezado como botón en lugar de encabezado.
collapsedbooleanfalseEstado inicial colapsado para grupos colapsables.
activebooleanfalseMantiene el grupo abierto cuando contiene la ruta activa.
headerClassNamestringundefinedClases extra para el encabezado (botón o título).
iconClassNamestringundefinedClases extra para el icono del grupo.
titleClassNamestringundefinedClases extra para el envoltorio del título.
contentClassNamestringundefinedClases extra para el envoltorio de hijos.
trailingReactNodeundefinedContenido personalizado al final del encabezado.
renderTitle({ title, isOpen }) => ReactNodeundefinedReemplazo completo del contenido del encabezado.
PropiedadTipoDefaultDescripción
labelstringRequeridoNombre visible del enlace.
hrefstringRequeridoRuta de destino.
activebooleanfalseEstado activo — se refleja vía data-active + aria-current="page".
iconElementTypeundefinedEnvoltorio del icono inicial.
badgestring | BadgeConfigundefinedEtiqueta de estado de notificación.
childrenReactNodeundefinedCuando se provee, reemplaza el contenido de label.
iconClassNamestringundefinedClases extra para el icono inicial.
labelClassNamestringundefinedClases extra para el texto de la etiqueta.
trailingReactNodeundefinedContenido personalizado tras la etiqueta.
depthnumberundefinedProfundidad de anidado — se refleja vía data-depth.

Props de Sidebar.SubGroupLink

Hereda todos los Sidebar.Link props más:

PropiedadTipoDefaultDescripción
isOpenbooleanfalseIndicador de visibilidad del estado colapsado.
onToggle() => voidundefinedCallback del gatillo de alternancia.
childrenReactNodeRequeridoEnlaces de navegación del subgrupo.
rootClassNamestringundefinedClases extra para el envoltorio del subgrupo.
linkClassNamestringundefinedClases extra para el enlace del subgrupo.
contentClassNamestringundefinedClases extra para el envoltorio de hijos anidados.
toggleClassNamestringundefinedClases extra para el botón expandir/colapsar.
renderToggle({ isOpen }) => ReactNodeundefinedReemplazo completo del botón de alternancia.

Props de Sidebar.ItemLink

PropiedadTipoDefaultDescripción
routeComponentRouteRequeridoObjeto de configuración de ruta a parsear.
activePathstringRequeridoRuta URL activa de la ventana.
activeRouteComponentRouteundefinedLa ruta activa resuelta (habilita la coincidencia por ruta de archivo).
depthnumber0Profundidad de anidado actual, usada por renderItem.
renderItem(route, depth) => ReactNodeundefinedReemplazo recursivo para rutas hijas.
classNamesSidebarSlotsundefinedOverrides de clase por pieza mezclados sobre los defaults.

Props de Sidebar.ItemsLink

PropiedadTipoDefaultDescripción
routesComponentRoute[]RequeridoArreglo de configuraciones de ruta a renderizar recursivamente.
classNamesSidebarSlotsundefinedOverrides de clase por pieza mezclados sobre los defaults.
componentItem(props: SidebarItemRenderProps) => ReactNodeundefinedReemplazo completo del render de un nodo (recursivo).
componentGroup(props: SidebarGroupRenderProps) => ReactNodeundefinedReemplazo completo del render de un grupo superior.

Slots (classNames)Link

Sidebar.Items acepta un mapa classNames que se mezcla sobre los defaults de la primitiva con tailwind-merge, por lo que tus overrides ganan sin marcadores !. Cada clave corresponde a una pieza del árbol automatizado. Los defaults son solo estructurales — el layout y los estados son tuyos para estilizar:

ClaveSe Aplica ADefault
itemraíz del enlace hojagroup flex items-center gap-2.5 outline-none
itemIconicono del enlace hojashrink-0
itemLabeletiqueta del enlace hojatruncate
groupenvoltorio del grupoundefined
groupHeaderencabezado del grupo (botón o título)undefined
groupIconicono inicial del grupoundefined
groupTitleenvoltorio del título del grupoflex items-center gap-2
groupContentenvoltorio de hijos del grupoflex flex-col
subgroupenvoltorio del subgrupoflex flex-col
subgroupLinkenlace del subgrupoflex-1 pr-8
subgroupContentenvoltorio de hijos del subgrupoflex flex-col
togglealternancia expandir/colapsar del subgrupoabsolute right-1
tsx
<Sidebar.Items
  routes={routes}
  classNames={{
    item: 'rounded-md px-2.5 py-1.5 text-sm transition-colors text-muted hover:bg-soft hover:text-body data-active:bg-primary-500/10 data-active:text-primary-400',
    groupHeader: 'px-2 mb-2 text-xs font-bold uppercase tracking-widest text-muted/60',
    toggle: 'size-7 rounded-md hover:bg-soft',
  }}
/>

Render PropsLink

componentItem y componentGroup son reemplazos estructurales: cuando se proveen, la pieza por defecto no se renderiza en absoluto, dándote control total del markup (por ejemplo, envoltorios de navegación personalizados o iconos por ruta).

Render PropProps Recibidas
componentItem{ route, activePath, activeRoute, isActive, depth }
componentGroup{ group, isGroupActive, children }
tsx
<Sidebar.Items
  routes={routes}
  componentItem={({ route, isActive, depth }) => (
    <CustomLink active={isActive} depth={depth} route={route} />
  )}
  componentGroup={({ group, isGroupActive, children }) => (
    <nav aria-label={group.title}>
      <h2>{group.title}</h2>
      {children}
    </nav>
  )}
/>
Last updated on October 18, 2026

Was this page helpful?