K
KoreUI

Sidebar

Shell

Navegación lateral colapsable, con drawer automático en móvil y persistencia en cookie. El estado lo resuelve el servidor, así que no hay parpadeo al cargar.

No se renderiza en vivo en esta página. El sidebar es parte del layout de página completa (posicionamiento fijo, drawer móvil, cookie de estado) y montarlo suelto dentro de un preview de docs rompería el layout. Pruébalo en la demo en vivo.

Uso básico

Normalmente va dentro de un shell, que es quien reserva el espacio del contenido.

<x-kore::sidebar>
    <x-slot:header>
        <span class="kore-sidebar-label font-bold">Mi App</span>
        <span class="kore-sidebar-mini font-bold">M</span>
    </x-slot:header>

    <x-kore::sidebar.item label="Panel" icon="layout-dashboard" route="dashboard" />
    <x-kore::sidebar.item label="Usuarios" icon="users" route="users.index" />
</x-kore::sidebar>
<x-kore::sidebar>
    <x-slot:header>
        <span class="kore-sidebar-label font-bold">Mi App</span>
        <span class="kore-sidebar-mini font-bold">M</span>
    </x-slot:header>

    <x-kore::sidebar.item label="Panel" icon="layout-dashboard" route="dashboard" />
    <x-kore::sidebar.item label="Usuarios" icon="users" route="users.index" />
</x-kore::sidebar>

La marca al colapsar

Cuando el sidebar se reduce a iconos, el nombre completo ya no cabe.

Dos clases resuelven qué se ve en cada estado:

  • kore-sidebar-label — se ve con el sidebar ancho, y se desvanece al colapsar.
  • kore-sidebar-mini — justo al revés: solo aparece en modo iconos.
Header con las dos versiones
<x-slot:header>
    <img src="/logo.svg" class="kore-sidebar-label h-8" />
    <img src="/isotipo.svg" class="kore-sidebar-mini h-8" />
</x-slot:header>
<x-slot:header>
    <img src="/logo.svg" class="kore-sidebar-label h-8" />
    <img src="/isotipo.svg" class="kore-sidebar-mini h-8" />
</x-slot:header>

Sin la versión mini, el hueco de la cabecera se queda vacío al colapsar.

Modo rail

El sidebar se muestra como iconos y se expande por encima del contenido al pasar el ratón, sin desplazarlo.

<x-kore::sidebar :rail="true">
    <x-kore::sidebar.item label="Panel" icon="layout-dashboard" route="dashboard" />
</x-kore::sidebar>
<x-kore::sidebar :rail="true">
    <x-kore::sidebar.item label="Panel" icon="layout-dashboard" route="dashboard" />
</x-kore::sidebar>

Ideal cuando el espacio horizontal es oro. También se expande al llegar con el teclado (Tab), no solo con el ratón: sin eso, quien navega tabulando no vería dónde está.

expandOnHover hace lo mismo pero partiendo de expandido en vez de colapsado.

Móvil: drawer con CSS puro

Por debajo del breakpoint, el sidebar se convierte en un drawer que entra desde el lado.

Con fondo oscuro y su botón de cierre. Se cierra al pulsar el fondo, con Escape, o al navegar con wire:navigate. Mientras está abierto, la página de detrás no hace scroll.

Todo esto es CSS, no JavaScript: es correcto en el primer paint y en cada cambio de tamaño de ventana. El drawer siempre se abre a ancho completo y con las etiquetas visibles, aunque en escritorio estuviera colapsado.

Varios sidebars a la vez

Con un id distinto y placement=right puedes tener un panel de herramientas a la derecha.

<x-kore::shell>
    <x-slot:sidebar>
        <x-kore::sidebar id="main">…</x-kore::sidebar>
    </x-slot:sidebar>

    <x-slot:aside>
        <x-kore::sidebar id="tools" placement="right" width="20rem">…</x-kore::sidebar>
    </x-slot:aside>

    {{ $slot }}
</x-kore::shell>
<x-kore::shell>
    <x-slot:sidebar>
        <x-kore::sidebar id="main">…</x-kore::sidebar>
    </x-slot:sidebar>

    <x-slot:aside>
        <x-kore::sidebar id="tools" placement="right" width="20rem">…</x-kore::sidebar>
    </x-slot:aside>

    {{ $slot }}
</x-kore::shell>

Cada uno recuerda su estado por separado, y el shell reserva el espacio de ambos.

El botón de menú

x-kore::sidebar.toggle habla con el store de Alpine, así que funciona desde cualquier parte de la página.

<x-kore::sidebar.toggle for="main" />
<x-kore::sidebar.toggle for="main" />

No necesita estar dentro del sidebar ni del shell. <x-kore::navbar> ya incluye uno por defecto (prop toggle); este componente suelto es para cuando quieres colocarlo en otro sitio.

Prop Tipo Default Descripción
forstring'main'El id del sidebar que controla
labelstring'Alternar navegación'aria-label del botón
iconstring'panel-left'Icono de Lucide

handleToggle() hace lo correcto según el dispositivo: en escritorio colapsa el sidebar y en móvil abre el drawer.

Flyouts en modo iconos

Con el sidebar colapsado, los sub-items no caben en línea.

Salen en un panel flotante al pasar el ratón. Los menús anidados se abren uno al lado del otro: entrar en un sub-menú de segundo nivel no cierra el panel del primero. Los items sin hijos muestran su nombre en un tooltip — hay uno solo por sidebar, reposicionado según el item activo, en vez de veinte tooltips montados a la vez.

Ver el detalle completo de sub-menús en sidebar.item.

Badges al colapsar

Un badge numérico se muda a la esquina del icono al colapsar el sidebar — es el único hueco que queda.

Por encima de badge_max (configurable, default 99), el contador se muestra como "99+". Un texto corto (!, new) pasa tal cual; cualquier otra cosa se degrada a un punto, para al menos dejar constancia de que ese item tiene algo.

El valor que se acorta es solo el visual. Un lector de pantalla sigue anunciando el número real, no 99+. Ver la API completa (badge, badgeVariant, badgeColor, badgeMax) en sidebar.item.

Store de Alpine

El estado vive en $store.koreSidebar, así que se puede leer y cambiar desde cualquier parte de la página, sin estar anidado dentro del sidebar.

Método Descripción
toggle(id)Colapsar / expandir (escritorio)
setCollapsed(id, bool)Fijar el estado
isCollapsed(id)¿Está en modo iconos?
openMobile(id) / closeMobile(id)Abrir / cerrar el drawer
isOpen(id)¿El drawer está abierto?
isMobile(id)¿El viewport está por debajo del breakpoint?
handleToggle(id)Colapsa en escritorio y abre el drawer en móvil. Es lo que quiere un botón de menú
Uso desde cualquier parte de la página
<button x-data x-on:click="$store.koreSidebar.toggle('main')">
    Colapsar
</button>

<div x-data x-show="$store.koreSidebar.isOpen('main')">
    El drawer está abierto
</div>
<button x-data x-on:click="$store.koreSidebar.toggle('main')">
    Colapsar
</button>

<div x-data x-show="$store.koreSidebar.isOpen('main')">
    El drawer está abierto
</div>

Eventos

Se despachan sobre window.

Evento Payload
kore:sidebar-toggle{ id, collapsed }
kore:sidebar-mobile-open{ id }
kore:sidebar-mobile-close{ id }

Props

Prop Tipo Default Descripción
id string main Identifica el sidebar en la cookie y en el store. Necesario si hay más de uno
collapsible bool config(true) Permite colapsarlo a modo iconos
collapsed bool config(false) Estado en la primera visita, antes de que exista cookie
placement left|right config(left) Lado de la pantalla
width string config(16rem) Ancho expandido. Longitud CSS, no clase de Tailwind
collapsedWidth string config(4rem) Ancho en modo iconos
breakpoint sm|md|lg|xl config(lg) Por debajo de esto, el sidebar es un drawer
persist bool config(true) Recordar el estado entre visitas (cookie kore_sidebar)
smart bool|null config(true) Detectar sola la ruta activa. Lo hereda cada item: manda el item, luego el sidebar, luego la configuración
navigate bool|null config(false) Añadir wire:navigate a los enlaces. Misma herencia que smart
overlay bool config(true) Fondo oscuro tras el drawer móvil
rail bool config(false) Modo rail: solo iconos, se expande sobre el contenido al pasar el ratón
expandOnHover bool config(false) Como rail, pero partiendo de expandido
ariaLabel string Sidebar Nombre de la región de navegación

Configuración

Personaliza los defaults en config/kore-ui.php, sección shell.sidebar.

config/kore-ui.php
'shell' => [
    'sidebar' => [
        'collapsible'      => true,
        'collapsed'        => false,   // estado en la primera visita, antes de que haya cookie
        'placement'        => 'left',  // 'left' | 'right'
        'width'            => '16rem',
        'collapsed_width'  => '4rem',
        'breakpoint'       => 'lg',    // por debajo de esto, el sidebar es un drawer
        'persist'          => true,
        'smart'            => true,    // detectar sola la ruta activa (llega a los items desde la 2.2)
        'navigate'         => false,   // añadir wire:navigate a los enlaces
        'overlay'          => true,    // fondo oscuro tras el drawer móvil
        'rail'             => false,
        'expand_on_hover'  => false,
        'duration'         => 200,     // ms de la animación
        'badge_max'        => 99,      // por encima, el contador muestra "99+"
    ],
],
'shell' => [
    'sidebar' => [
        'collapsible'      => true,
        'collapsed'        => false,   // estado en la primera visita, antes de que haya cookie
        'placement'        => 'left',  // 'left' | 'right'
        'width'            => '16rem',
        'collapsed_width'  => '4rem',
        'breakpoint'       => 'lg',    // por debajo de esto, el sidebar es un drawer
        'persist'          => true,
        'smart'            => true,    // detectar sola la ruta activa (llega a los items desde la 2.2)
        'navigate'         => false,   // añadir wire:navigate a los enlaces
        'overlay'          => true,    // fondo oscuro tras el drawer móvil
        'rail'             => false,
        'expand_on_hover'  => false,
        'duration'         => 200,     // ms de la animación
        'badge_max'        => 99,      // por encima, el contador muestra "99+"
    ],
],

Los anchos son longitudes CSS, no clases de Tailwind: alimentan las custom properties que gobiernan el layout.

Cargando