--- title: "sidebar" description: "Composable, collapsible application sidebar with mobile off-canvas, icon mode, and keyboard toggle." category: "Layout" --- # sidebar Composable, collapsible application sidebar with mobile off-canvas, icon mode, and keyboard toggle. ## Installation ```bash php artisan ui:add sidebar ``` ## Dependencies - composer: `gehrisandro/tailwind-merge-laravel` ## Usage ```blade {{-- open sets the starting state; after that a cookie and Ctrl/Cmd-B win --}} Acme Platform @foreach ($links as $link) {{ $link['label'] }} @endforeach {{ $slot }} ``` ## Example The demo rendered on the docs page. ```blade {{-- The sidebar is a full-height, fixed-position layout primitive, so it cannot embed directly inside the docs column. We preview the real sidebar-01 block in an iframe; open it standalone for the full experience. --}}
Open the sidebar-01 block full screen →
``` ## Source Copy each file to the path shown above it. `resources/views/components/ui/sidebar/provider.blade.php` ```blade @props([ 'open' => true, ]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
@once @endonce ``` `resources/views/components/ui/sidebar/index.blade.php` ```blade @props([ 'side' => 'left', 'collapsible' => 'icon', ]) @php $isIcon = $collapsible === 'icon'; $offscreen = $side === 'right' ? 'translate-x-full' : '-translate-x-full'; // Layout gap the sidebar reserves: full → icon width → 0 (offcanvas). $gapWidth = $isIcon ? 'w-(--sidebar-width) group-data-[state=collapsed]:w-(--sidebar-width-icon)' : 'w-(--sidebar-width) group-data-[state=collapsed]:w-0'; // The fixed panel: icon mode shrinks its width; offcanvas keeps full width // and slides off-screen (so icon-mode tooltips are never clipped). // Every variant class below is written out in full: Tailwind scans source // text, so a class assembled by interpolation never gets compiled. $panelWidth = match (true) { $isIcon => 'w-(--sidebar-width) group-data-[state=collapsed]:w-(--sidebar-width-icon)', $side === 'right' => 'w-(--sidebar-width) group-data-[state=collapsed]:translate-x-full', default => 'w-(--sidebar-width) group-data-[state=collapsed]:-translate-x-full', }; @endphp {{-- Desktop sidebar. data-state / data-collapsible are kept in sync by the script that ships with . --}} {{-- Mobile sidebar: a native , so it lands in the top layer with a focus trap and Esc handling already wired. --}} {{-- Not bg-black/50. Tailwind v4 compiles that to oklab(0 0 0 / 0.5), and Chrome does not paint a modern colour function with alpha inside the dialog top layer, so the scrim silently renders as nothing. A legacy rgb() with alpha composites correctly in the same place. --}}
{{ $slot }}
``` `resources/views/components/ui/sidebar/trigger.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'inline-flex size-7 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/rail.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 cursor-w-resize transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/inset.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'relative flex min-h-svh flex-1 flex-col bg-background', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/header.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex flex-col gap-2 p-2', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/footer.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex flex-col gap-2 p-2', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/content.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/group.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'relative flex w-full min-w-0 flex-col p-2', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/group-label.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/group-content.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'w-full text-sm', $attributes->get('class'), ); @endphp
except('class')->merge(['class' => $classes]) }}> {{ $slot }}
``` `resources/views/components/ui/sidebar/group-action.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 group-data-[collapsible=icon]:hidden', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/menu.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex w-full min-w-0 flex-col gap-1', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/menu-item.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'group/menu-item relative', $attributes->get('class'), ); @endphp
  • except('class')->merge(['class' => $classes]) }}> {{ $slot }}
  • ``` `resources/views/components/ui/sidebar/menu-button.blade.php` ```blade @props([ 'variant' => 'default', 'size' => 'default', 'isActive' => false, 'href' => null, 'tooltip' => null, ]) @php $base = 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0'; $variants = match ($variant) { 'outline' => 'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]', default => '', }; $sizes = match ($size) { 'sm' => 'h-7 text-xs', 'lg' => 'h-12 text-sm group-data-[collapsible=icon]:p-0!', default => 'h-8 text-sm', }; $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( $base, $variants, $sizes, $attributes->get('class'), ); $tag = $href ? 'a' : 'button'; @endphp {{-- The button and tooltip stay SIBLINGS (no wrapper) so menu-badge/menu-action keep their `peer/menu-button` relationship. The tooltip is pure CSS: shown only when the sidebar is collapsed to icons and the button is hovered. --}} <{{ $tag }} data-sidebar="menu-button" data-size="{{ $size }}" data-active="{{ $isActive ? 'true' : 'false' }}" @if ($href) href="{{ $href }}" @else type="button" @endif {{ $attributes->except('class')->merge(['class' => $classes]) }}> {{ $slot }} @if ($tooltip) @endif ``` `resources/views/components/ui/sidebar/menu-action.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0 group-data-[collapsible=icon]:hidden', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/menu-badge.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 group-data-[collapsible=icon]:hidden', $attributes->get('class'), ); @endphp
    except('class')->merge(['class' => $classes]) }}> {{ $slot }}
    ``` `resources/views/components/ui/sidebar/menu-skeleton.blade.php` ```blade @props([ 'showIcon' => false, ]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex h-8 items-center gap-2 rounded-md px-2', $attributes->get('class'), ); @endphp
    except('class')->merge(['class' => $classes]) }}> @if ($showIcon)
    @endif
    ``` `resources/views/components/ui/sidebar/menu-sub.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden', $attributes->get('class'), ); @endphp ``` `resources/views/components/ui/sidebar/menu-sub-item.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'group/menu-sub-item relative', $attributes->get('class'), ); @endphp
  • except('class')->merge(['class' => $classes]) }}> {{ $slot }}
  • ``` `resources/views/components/ui/sidebar/menu-sub-button.blade.php` ```blade @props([ 'size' => 'md', 'isActive' => false, 'href' => null, ]) @php $sizes = match ($size) { 'sm' => 'text-xs', default => 'text-sm', }; $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden', $sizes, $attributes->get('class'), ); $tag = $href ? 'a' : 'button'; @endphp <{{ $tag }} data-sidebar="menu-sub-button" data-size="{{ $size }}" data-active="{{ $isActive ? 'true' : 'false' }}" @if ($href) href="{{ $href }}" @else type="button" @endif {{ $attributes->except('class')->merge(['class' => $classes]) }}> {{ $slot }} ``` `resources/views/components/ui/sidebar/separator.blade.php` ```blade @props([]) @php $classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge( 'mx-2 h-px w-auto shrink-0 bg-sidebar-border', $attributes->get('class'), ); @endphp
    except('class')->merge(['class' => $classes]) }}>
    ``` ## Theme tokens `--sidebar`, `--sidebar-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring`, `--primary`, `--primary-foreground`, `--background` Ships its own inline