Zaidan

Command Palette

Search for a command to run...

GitHub69

Sidebar

A composable, themeable and customizable sidebar component.

Installation

CLI

Manual

Install the following dependencies:

Copy and paste the following code into your project.

import type { PolymorphicProps } from "@kobalte/core";
import { Polymorphic } from "@kobalte/core";
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import { Menu } from "lucide-solid";
import type { Accessor, Component, ComponentProps, JSX, ValidComponent } from "solid-js";
import {
createContext,
createEffect,
createMemo,
createSignal,
Match,
mergeProps,
onCleanup,
Show,
Switch,
splitProps,
useContext,
} from "solid-js";
import { cn } from "~/lib/utils";
import { useIsMobile } from "~/lib/hooks/use-mobile";
import type { ButtonProps } from "~/components/ui/button";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Separator } from "~/components/ui/separator";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "~/components/ui/sheet";
import { Skeleton } from "~/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = {
state: Accessor<"expanded" | "collapsed">;
open: Accessor<boolean>;
setOpen: (open: boolean) => void;
openMobile: Accessor<boolean>;
setOpenMobile: (open: boolean) => void;
isMobile: Accessor<boolean>;
toggleSidebar: () => void;
};
const SidebarContext = createContext<SidebarContextProps | null>(null);
function useSidebar() {
const context = useContext(SidebarContext);
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.");
}
return context;
}
type SidebarProviderProps = ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
};
const SidebarProvider = (props: SidebarProviderProps) => {
const mergedProps = mergeProps({ defaultOpen: true }, props);
const [local, others] = splitProps(mergedProps, [
"defaultOpen",
"open",
"onOpenChange",
"class",
"style",
"children",
]);
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = createSignal(false);
// This is the internal state of the sidebar.
// We use open and onOpenChange for control from outside the component.
const [_open, _setOpen] = createSignal(local.defaultOpen);
const open = () => local.open ?? _open();
const setOpen = (value: boolean | ((value: boolean) => boolean)) => {
if (local.onOpenChange) {
return local.onOpenChange?.(typeof value === "function" ? value(open()) : value);
}
_setOpen(value);
// This sets the cookie to keep the sidebar state.
// biome-ignore lint/suspicious/noDocumentCookie: <waiting for better solution>
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open()}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
};
// Helper to toggle the sidebar.
const toggleSidebar = () => {
return isMobile() ? setOpenMobile((open) => !open) : setOpen((open) => !open);
};
// Adds a keyboard shortcut to toggle the sidebar.
createEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
toggleSidebar();
}
};
window.addEventListener("keydown", handleKeyDown);
onCleanup(() => window.removeEventListener("keydown", handleKeyDown));
});
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = () => (open() ? "expanded" : "collapsed");
const contextValue = {
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
};
return (
<SidebarContext.Provider value={contextValue}>
<div
data-slot="sidebar-wrapper"
style={{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...(local.style as JSX.CSSProperties),
}}
class={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
local.class,
)}
{...others}
>
{local.children}
</div>
</SidebarContext.Provider>
);
};
type SidebarProps = ComponentProps<"div"> & {
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
};
const Sidebar: Component<SidebarProps> = (props) => {
const mergedProps = mergeProps<SidebarProps[]>(
{
side: "left",
variant: "sidebar",
collapsible: "offcanvas",
},
props,
);
const [local, others] = splitProps(mergedProps, [
"side",
"variant",
"collapsible",
"class",
"children",
]);
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
return (
<Switch>
<Match when={local.collapsible === "none"}>
<div
class={cn(
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
local.class,
)}
data-slot="sidebar"
{...others}
>
{local.children}
</div>
</Match>
<Match when={isMobile()}>
<Sheet onOpenChange={setOpenMobile} open={openMobile()} {...others}>
<SheetContent
class="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
data-mobile="true"
data-sidebar="sidebar"
data-slot="sidebar"
side={local.side}
style={{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
}}
>
<SheetHeader class="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div class="flex size-full flex-col">{local.children}</div>
</SheetContent>
</Sheet>
</Match>
<Match when={!isMobile()}>
<div
class="group peer hidden text-sidebar-foreground md:block"
data-state={state()}
data-collapsible={state() === "collapsed" ? local.collapsible : ""}
data-variant={local.variant}
data-side={local.side}
data-slot="sidebar"
>
{/* This is what handles the sidebar gap on desktop */}
<div
data-slot="sidebar-gap"
class={cn(
"relative z-sidebar-gap w-(--sidebar-width) bg-transparent",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
local.variant === "floating" || local.variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
)}
/>
<div
data-slot="sidebar-container"
class={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
local.side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
local.variant === "floating" || local.variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
local.class,
)}
{...others}
>
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
class="z-sidebar-inner flex size-full flex-col"
>
{local.children}
</div>
</div>
</div>
</Match>
</Switch>
);
};
type SidebarTriggerProps = ButtonProps;
const SidebarTrigger = (props: SidebarTriggerProps) => {
const [local, others] = splitProps(props, ["class", "onClick"]);
const { toggleSidebar } = useSidebar();
return (
<Button
data-sidebar="trigger"
data-slot="sidebar-trigger"
variant="ghost"
size="icon-sm"
class={cn("z-sidebar-trigger", local.class)}
onClick={(event: MouseEvent) => {
//@ts-expect-error - TODO: Typescript wizardry needed here
local.onClick?.(event);
toggleSidebar();
}}
{...others}
>
<Menu />
<span class="sr-only">Toggle Sidebar</span>
</Button>
);
};
const SidebarRail = (props: ComponentProps<"button">) => {
const [local, others] = splitProps(props, ["class"]);
const { toggleSidebar } = useSidebar();
return (
<button
data-sidebar="rail"
data-slot="sidebar-rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
class={cn(
"absolute inset-y-0 z-20 z-sidebar-rail hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
local.class,
)}
{...others}
/>
);
};
const SidebarInset = (props: ComponentProps<"main">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<main
data-slot="sidebar-inset"
class={cn("relative z-sidebar-inset flex w-full flex-1 flex-col", local.class)}
{...others}
/>
);
};
const SidebarInput = (props: ComponentProps<typeof Input>) => {
const [local, others] = splitProps(props, ["class"]);
return (
<Input
data-slot="sidebar-input"
data-sidebar="input"
class={cn("z-sidebar-input", local.class)}
{...others}
/>
);
};
const SidebarHeader = (props: ComponentProps<"div">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-header"
data-sidebar="header"
class={cn("z-sidebar-header flex flex-col", local.class)}
{...others}
/>
);
};
const SidebarFooter = (props: ComponentProps<"div">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-footer"
data-sidebar="footer"
class={cn("z-sidebar-footer flex flex-col", local.class)}
{...others}
/>
);
};
type SidebarSeparatorProps<T extends ValidComponent = "hr"> = PolymorphicProps<
T,
ComponentProps<typeof Separator<T>>
>;
const SidebarSeparator = <T extends ValidComponent = "hr">(props: SidebarSeparatorProps<T>) => {
const [local, others] = splitProps(props as SidebarSeparatorProps, ["class"]);
return (
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
class={cn("z-sidebar-separator w-auto", local.class)}
{...others}
/>
);
};
const SidebarContent = (props: ComponentProps<"div">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-content"
data-sidebar="content"
class={cn(
"z-sidebar-content flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden",
local.class,
)}
{...others}
/>
);
};
const SidebarGroup = (props: ComponentProps<"div">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-group"
data-sidebar="group"
class={cn("relative z-sidebar-group flex w-full min-w-0 flex-col", local.class)}
{...others}
/>
);
};
type SidebarGroupLabelProps<T extends ValidComponent = "div"> = PolymorphicProps<
T,
ComponentProps<T>
>;
const SidebarGroupLabel = <T extends ValidComponent = "div">(props: SidebarGroupLabelProps<T>) => {
const [local, others] = splitProps(props as SidebarGroupLabelProps, ["class"]);
return (
<Polymorphic<SidebarGroupLabelProps>
as="div"
data-slot="sidebar-group-label"
data-sidebar="group-label"
class={cn(
"z-sidebar-group-label flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
local.class,
)}
{...others}
/>
);
};
type SidebarGroupActionProps<T extends ValidComponent = "button"> = PolymorphicProps<
T,
ComponentProps<T>
>;
const SidebarGroupAction = <T extends ValidComponent = "button">(
props: SidebarGroupActionProps<T>,
) => {
const [local, others] = splitProps(props as SidebarGroupActionProps, ["class"]);
return (
<Polymorphic<SidebarGroupActionProps>
as="button"
data-slot="sidebar-group-action"
data-sidebar="group-action"
class={cn(
"z-sidebar-group-action flex aspect-square items-center justify-center outline-hidden transition-transform after:absolute after:-inset-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:shrink-0",
local.class,
)}
{...others}
/>
);
};
const SidebarGroupContent = (props: ComponentProps<"div">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-group-content"
data-sidebar="group-content"
class={cn("z-sidebar-group-content w-full", local.class)}
{...others}
/>
);
};
const SidebarMenu = (props: ComponentProps<"ul">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ul
data-slot="sidebar-menu"
data-sidebar="menu"
class={cn("z-sidebar-menu flex w-full min-w-0 flex-col", local.class)}
{...others}
/>
);
};
const SidebarMenuItem = (props: ComponentProps<"li">) => {
const [local, others] = splitProps(props, ["class"]);
return (
<li
data-slot="sidebar-menu-item"
data-sidebar="menu-item"
class={cn("group/menu-item relative", local.class)}
{...others}
/>
);
};
const sidebarMenuButtonVariants = cva(
"peer/menu-button group/menu-button z-sidebar-menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "z-sidebar-menu-button-variant-default",
outline: "z-sidebar-menu-button-variant-outline",
},
size: {
default: "z-sidebar-menu-button-size-default",
sm: "z-sidebar-menu-button-size-sm",
lg: "z-sidebar-menu-button-size-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
type SidebarMenuButtonProps<T extends ValidComponent = "button"> = PolymorphicProps<
T,
Pick<ComponentProps<T>, "class" | "children">
> &
VariantProps<typeof sidebarMenuButtonVariants> & {
isActive?: boolean;
tooltip?: string;
};
const SidebarMenuButton = <T extends ValidComponent = "button">(
rawProps: SidebarMenuButtonProps<T>,
) => {
const props = mergeProps({ isActive: false, variant: "default", size: "default" }, rawProps);
const [local, others] = splitProps(props as SidebarMenuButtonProps, [
"isActive",
"tooltip",
"variant",
"size",
"class",
]);
const { isMobile, state } = useSidebar();
const MenuButton = (props: SidebarMenuButtonProps) => {
const [_local, _others] = splitProps(props as SidebarMenuButtonProps, ["class"]);
return (
<Polymorphic<SidebarMenuButtonProps>
as="button"
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={local.size}
data-active={local.isActive}
class={cn(
sidebarMenuButtonVariants({ variant: local.variant, size: local.size }),
_local.class,
local.class,
)}
{..._others}
{...others}
/>
);
};
return (
<Show fallback={<MenuButton />} when={local.tooltip}>
<Tooltip placement="right">
<TooltipTrigger as={MenuButton} class="w-full" />
<TooltipContent hidden={state() !== "collapsed" || isMobile()}>
{local.tooltip}
</TooltipContent>
</Tooltip>
</Show>
);
};
type SidebarMenuActionProps<T extends ValidComponent = "button"> = ComponentProps<T> & {
showOnHover?: boolean;
};
const SidebarMenuAction = <T extends ValidComponent = "button">(
rawProps: PolymorphicProps<T, SidebarMenuActionProps<T>>,
) => {
const props = mergeProps({ showOnHover: false }, rawProps);
const [local, others] = splitProps(props as SidebarMenuActionProps, ["class", "showOnHover"]);
return (
<Polymorphic<SidebarMenuActionProps>
as="button"
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
class={cn(
"z-sidebar-menu-action flex items-center justify-center outline-hidden transition-transform after:absolute after:-inset-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:shrink-0",
local.showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-open:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0",
local.class,
)}
{...others}
/>
);
};
const SidebarMenuBadge: Component<ComponentProps<"div">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
class={cn(
"z-sidebar-menu-badge flex select-none items-center justify-center tabular-nums group-data-[collapsible=icon]:hidden",
local.class,
)}
{...others}
/>
);
};
type SidebarMenuSkeletonProps = ComponentProps<"div"> & {
showIcon?: boolean;
};
const SidebarMenuSkeleton: Component<SidebarMenuSkeletonProps> = (rawProps) => {
const props = mergeProps({ showIcon: false }, rawProps);
const [local, others] = splitProps(props, ["class", "showIcon"]);
// Random width between 50 to 90%.
const width = createMemo(() => `${Math.floor(Math.random() * 40) + 50}%`);
return (
<div
data-slot="sidebar-menu-skeleton"
data-sidebar="menu-skeleton"
class={cn("z-sidebar-menu-skeleton flex items-center", local.class)}
{...others}
>
<Show when={local.showIcon}>
<Skeleton class="z-sidebar-menu-skeleton-icon" data-sidebar="menu-skeleton-icon" />
</Show>
<Skeleton
class="z-sidebar-menu-skeleton-text max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={{
"--skeleton-width": width(),
}}
/>
</div>
);
};
const SidebarMenuSub: Component<ComponentProps<"ul">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ul
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
class={cn("z-sidebar-menu-sub flex min-w-0 flex-col", local.class)}
{...others}
/>
);
};
const SidebarMenuSubItem: Component<ComponentProps<"li">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<li
data-slot="sidebar-menu-sub-item"
data-sidebar="menu-sub-item"
class={cn("group/menu-sub-item relative", local.class)}
{...others}
/>
);
};
type SidebarMenuSubButtonProps<T extends ValidComponent = "a"> = ComponentProps<T> & {
size?: "sm" | "md";
isActive?: boolean;
};
const SidebarMenuSubButton = <T extends ValidComponent = "a">(
rawProps: PolymorphicProps<T, SidebarMenuSubButtonProps<T>>,
) => {
const props = mergeProps({ size: "md" }, rawProps);
const [local, others] = splitProps(props as SidebarMenuSubButtonProps, [
"size",
"isActive",
"class",
]);
return (
<Polymorphic<SidebarMenuSubButtonProps>
as="a"
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={local.size}
data-active={local.isActive}
class={cn(
"z-sidebar-menu-sub-button flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:shrink-0",
local.class,
)}
{...others}
/>
);
};
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
type SidebarProps,
};

The sidebar component supports multiple variants and collapsible modes:

Variants

  • sidebar - Default sidebar attached to the edge
  • floating - Sidebar with rounded corners and spacing from edges
  • inset - Sidebar inset within the main content area

Collapsible Modes

  • offcanvas - Slides in/out from the edge (default)
  • icon - Collapses to show only icons
  • none - Non-collapsible fixed sidebar

Props

PropTypeDefaultDescription
side"left" | "right""left"Which side the sidebar appears on
variant"sidebar" | "floating" | "inset""sidebar"Visual style variant
collapsible"offcanvas" | "icon" | "none""offcanvas"How the sidebar collapses

SidebarProvider

PropTypeDefaultDescription
defaultOpenbooleantrueWhether sidebar starts open
openboolean-Controlled open state
onOpenChange(open: boolean) => void-Callback when open state changes

Keyboard Shortcuts

  • Cmd+B (Mac) / Ctrl+B (Windows) - Toggle sidebar

Examples

Here is the source code of the example from the preview page:

Basic

import { Check, ChevronsUpDown, Search } from "lucide-solid";
import { createSignal, For, Show } from "solid-js";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "~/components/ui/breadcrumb";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { Label } from "~/components/ui/label";
import { Separator } from "~/components/ui/separator";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "~/components/ui/sidebar";
export default function SidebarExample() {
const [selectedVersion, setSelectedVersion] = createSignal(data.versions[0]);
return (
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
as={SidebarMenuButton}
size="lg"
class="data-expanded:bg-sidebar-accent data-expanded:text-sidebar-accent-foreground"
>
<div class="flex flex-col gap-0.5 leading-none">
<span class="font-medium">Documentation</span>
<span class="text-muted-foreground text-xs">v{selectedVersion()}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</DropdownMenuTrigger>
<DropdownMenuContent class="w-(--kb-popper-anchor-width)">
<For each={data.versions}>
{(version) => (
<DropdownMenuItem onSelect={() => setSelectedVersion(version)}>
v{version}
<Show when={version === selectedVersion()}>
<Check class="ml-auto" />
</Show>
</DropdownMenuItem>
)}
</For>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<form>
<SidebarGroup class="py-0">
<SidebarGroupContent class="relative">
<Label for="search" class="sr-only">
Search
</Label>
<SidebarInput id="search" placeholder="Search the docs..." class="pl-8" />
<Search class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 select-none opacity-50" />
</SidebarGroupContent>
</SidebarGroup>
</form>
</SidebarHeader>
<SidebarContent>
<For each={data.navMain}>
{(item) => (
<SidebarGroup>
<SidebarGroupLabel>{item.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<For each={item.items}>
{(subItem) => (
<SidebarMenuItem>
<SidebarMenuButton as="a" href={subItem.url} isActive={subItem.isActive}>
{subItem.title}
</SidebarMenuButton>
</SidebarMenuItem>
)}
</For>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)}
</For>
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink href="#">Building Your Application</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator class="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div class="flex flex-1 flex-col gap-4 p-4">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
</div>
<div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
);
}

See Also