Caprice LogoCaprice UI

Sidebar

A composable, themeable and customizable sidebar component.

A sidebar that collapses to icons.

Sidebars are one of the most complex components to build. They are central to any application and often contain a lot of moving parts.

I don't like building sidebars. So I built 30+ of them. All kinds of configurations. Then I extracted the core components into sidebar.tsx.

We now have a solid foundation to build on top of. Composable. Themeable. Customizable.

Installation

npx shadcn@latest add @caprice/sidebar

Structure

A Sidebar component is composed of the following parts:

  • Sidebar.Provider - Handles collapsible state.
  • Sidebar.Root - The sidebar container.
  • Sidebar.Header and Sidebar.Footer - Sticky at the top and bottom of the sidebar.
  • Sidebar.Content - Scrollable content.
  • Sidebar.Group - Section within the Sidebar.Content.
  • Sidebar.Trigger - Trigger for the Sidebar.Root.
Sidebar Structure

Usage

import * as Sidebar from "@/components/caprice-ui/sidebar"
app/layout.tsx
import * as Sidebar from "@/components/caprice-ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <Sidebar.Provider>
      <AppSidebar />
      <main>
        <Sidebar.Trigger />
        {children}
      </main>
    </Sidebar.Provider>
  )
}
components/app-sidebar.tsx
import * as Sidebar from "@/components/caprice-ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Header />
      <Sidebar.Content>
        <Sidebar.Group />
        <Sidebar.Group />
      </Sidebar.Content>
      <Sidebar.Footer />
    </Sidebar.Root>
  )
}

Your First Sidebar

Let's start with the most basic sidebar. A collapsible sidebar with a menu.

Add a Sidebar.Provider and Sidebar.Trigger at the root of your application.

app/layout.tsx
import * as Sidebar from "@/components/caprice-ui/sidebar";
import { AppSidebar } from "@/components/app-sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <Sidebar.Provider>
      <AppSidebar />
      <main>
        <Sidebar.Trigger />
        {children}
      </main>
    </Sidebar.Provider>
  )
}
Create a new sidebar component at components/app-sidebar.tsx.
components/app-sidebar.tsx
import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Content />
    </Sidebar.Root>
  )
}
Now, let's add a Sidebar.Menu to the sidebar.

We'll use the Sidebar.Menu component in a Sidebar.Group.

components/app-sidebar.tsx
import { Calendar, Home, Inbox, Search, Settings } from "lucide-react";
import * as Sidebar from "@/components/caprice-ui/sidebar";

// Menu items.
const items = [
  {
    title: "Home",
    url: "#",
    icon: Home,
  },
  {
    title: "Inbox",
    url: "#",
    icon: Inbox,
  },
  {
    title: "Calendar",
    url: "#",
    icon: Calendar,
  },
  {
    title: "Search",
    url: "#",
    icon: Search,
  },
  {
    title: "Settings",
    url: "#",
    icon: Settings,
  },
]

export function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Content>
        <Sidebar.Group>
          <Sidebar.GroupLabel>Application</Sidebar.GroupLabel>
          <Sidebar.GroupContent>
            <Sidebar.Menu>
              {items.map((item) => (
                <Sidebar.MenuItem key={item.title}>
                  <Sidebar.MenuButton render={
                     <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  } />
                </Sidebar.MenuItem>
              ))}
            </Sidebar.Menu>
          </Sidebar.GroupContent>
        </Sidebar.Group>
      </Sidebar.Content>
    </Sidebar.Root>
  )
}
You've created your first sidebar.

You should see something like this:

Your first sidebar.

Components

The components in sidebar.tsx are built to be composable i.e you build your sidebar by putting the provided components together. They also compose well with other shadcn/ui & Caprice UI components such as Menu, Collapsible or Dialog etc.

If you need to change the code in sidebar.tsx, you are encouraged to do so. The code is yours. Use sidebar.tsx as a starting point and build your own.

In the next sections, we'll go over each component and how to use them.

Sidebar.Provider

The Sidebar.Provider component is used to provide the sidebar context to the Sidebar.Root component. You should always wrap your application in a Sidebar.Provider component.

Props

NameTypeDescription
defaultOpenbooleanDefault open state of the sidebar.
openbooleanOpen state of the sidebar (controlled).
onOpenChange(open: boolean) => voidSets open state of the sidebar (controlled).

Width

If you have a single sidebar in your application, you can use the SIDEBAR_WIDTH and SIDEBAR_WIDTH_MOBILE variables in sidebar.tsx to set the width of the sidebar.

components/ui/sidebar.tsx
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"

For multiple sidebars in your application, you can use the style prop to set the width of the sidebar.

To set the width of the sidebar, you can use the --sidebar-width and --sidebar-width-mobile CSS variables in the style prop.

components/ui/sidebar.tsx
<Sidebar.Provider
  style={{
    "--sidebar-width": "20rem",
    "--sidebar-width-mobile": "20rem",
  }}
>
  <Sidebar.Root />
</Sidebar.Provider>

This will handle the width of the sidebar but also the layout spacing.

Keyboard Shortcut

The SIDEBAR_KEYBOARD_SHORTCUT variable is used to set the keyboard shortcut used to open and close the sidebar.

To trigger the sidebar, you use the cmd+b keyboard shortcut on Mac and ctrl+b on Windows.

You can change the keyboard shortcut by updating the SIDEBAR_KEYBOARD_SHORTCUT variable.

components/ui/sidebar.tsx
const SIDEBAR_KEYBOARD_SHORTCUT = "b"

Persisted State

The Sidebar.Provider supports persisting the sidebar state across page reloads and server-side rendering. It uses cookies to store the current state of the sidebar. When the sidebar state changes, a default cookie named sidebar_state is set with the current open/closed state. This cookie is then read on subsequent page loads to restore the sidebar state.

To persist sidebar state in Next.js, set up your Sidebar.Provider in app/layout.tsx like this:

app/layout.tsx
import { cookies } from "next/headers"
import * as Sidebar from "@/components/caprice-ui/sidebar";
import { AppSidebar } from "@/components/app-sidebar"

export async function Layout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies()
  const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"

  return (
    <Sidebar.Provider defaultOpen={defaultOpen}>
      <AppSidebar />
      <main>
        <Sidebar.Trigger />
        {children}
      </main>
    </Sidebar.Provider>
  )
}

You can change the name of the cookie by updating the SIDEBAR_COOKIE_NAME variable in sidebar.tsx.

components/ui/sidebar.tsx
const SIDEBAR_COOKIE_NAME = "sidebar_state"

Sidebar.Root

The main Sidebar.Root component used to render a collapsible sidebar.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return <Sidebar.Root />
}

Props

PropertyTypeDescription
sideleft or rightThe side of the sidebar.
variantsidebar, floating, or insetThe variant of the sidebar.
collapsibleoffcanvas, icon, or noneCollapsible state of the sidebar.

side

Use the side prop to change the side of the sidebar.

Available options are left and right.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return <Sidebar.Root side="left | right" />
}

variant

Use the variant prop to change the variant of the sidebar.

Available options are sidebar, floating and inset.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return <Sidebar.Root variant="sidebar | floating | inset" />
}

Note: If you use the inset variant, remember to wrap your main content in a SidebarInset component.

<Sidebar.Provider>
  <Sidebar.Root variant="inset" />
  <Sidebar.Inset>
    <main>{children}</main>
  </Sidebar.Inset>
</Sidebar.Provider>

collapsible

Use the collapsible prop to make the sidebar collapsible.

Available options are offcanvas, icon and none.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return <Sidebar.Root collapsible="offcanvas | icon | none" />
}
PropDescription
offcanvasA collapsible sidebar that slides in from the left or right.
iconA sidebar that collapses to icons.
noneA non-collapsible sidebar.

useSidebar

The useSidebar hook is used to control the sidebar.

import { useSidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  const {
    state,
    open,
    setOpen,
    openMobile,
    setOpenMobile,
    isMobile,
    toggleSidebar,
  } = useSidebar()
}
PropertyTypeDescription
stateexpanded or collapsedThe current state of the sidebar.
openbooleanWhether the sidebar is open.
setOpen(open: boolean) => voidSets the open state of the sidebar.
openMobilebooleanWhether the sidebar is open on mobile.
setOpenMobile(open: boolean) => voidSets the open state of the sidebar on mobile.
isMobilebooleanWhether the sidebar is on mobile.
toggleSidebar() => voidToggles the sidebar. Desktop and mobile.

Sidebar.Header

Use the Sidebar.Header component to add a sticky header to the sidebar.

The following example adds a <Menu> to the Sidebar.Header.

A sidebar header with a dropdown menu.

components/app-sidebar.tsx
<Sidebar.Root>
  <Sidebar.Header>
    <Sidebar.Menu>
      <Sidebar.MenuItem>
        <Menu.Root>
          <Menu.Trigger render={
            <Sidebar.MenuButton>
              Select Workspace
              <ChevronDown className="ml-auto" />
            </Sidebar.MenuButton>
          } />
          <Menu.Positioner>
            <Menu.Popup className="w-(--anchor-width)">
              <Menu.Item>
                <span>Acme Inc</span>
              </Menu.Item>
              <Menu.Item>
                <span>Acme Corp.</span>
              </Menu.Item>
            </Menu.Popup>
          </Menu.Positioner>
        </Menu.Root>
      </Sidebar.MenuItem>
    </Sidebar.Menu>
  </Sidebar.Header>
</Sidebar.Root>

Sidebar.Footer

Use the Sidebar.Footer component to add a sticky footer to the sidebar.

The following example adds a <Menu> to the Sidebar.Footer.

A sidebar footer with a dropdown menu.

components/app-sidebar.tsx
export function AppSidebar() {
  return (
    <Sidebar.Provider>
      <Sidebar.Root>
        <Sidebar.Header />
        <Sidebar.Content />
        <Sidebar.Footer>
          <Sidebar.Menu>
            <Sidebar.MenuItem>
              <Menu.Root>
                <Menu.Trigger render={
                  <Sidebar.MenuButton>
                    <User2 /> Username
                    <ChevronUp className="ml-auto" />
                  </Sidebar.MenuButton>
                } />
                <Menu.Positioner side="top">
                  <Menu.Popup className="w-(--anchor-width)">
                    <Menu.Item>
                      <span>Account</span>
                    </Menu.Item>
                    <Menu.Item>
                      <span>Billing</span>
                    </Menu.Item>
                    <Menu.Item>
                      <span>Sign out</span>
                    </Menu.Item>
                  </Menu.Popup>
                </Menu.Positioner>
              </Menu.Root>
            </Sidebar.MenuItem>
          </Sidebar.Menu>
        </Sidebar.Footer>
      </Sidebar.Root>
    </Sidebar.Provider>
  )
}

Sidebar.Content

The Sidebar.Content component is used to wrap the content of the sidebar. This is where you add your Sidebar.Group components. It is scrollable.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Positioner>
        <Sidebar.Content>
          <Sidebar.Group />
          <Sidebar.Group />
        </Sidebar.Content>
      </Sidebar.Positioner>
    </Sidebar.Root>
  )
}

Sidebar.Group

Use the Sidebar.Group component to create a section within the sidebar.

A Sidebar.Group has a Sidebar.GroupLabel, a Sidebar.GroupContent and an optional Sidebar.GroupAction.

A sidebar group.

import * as Sidebar from "@/components/caprice-ui/sidebar";

export function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Content>
        <Sidebar.Group>
          <Sidebar.GroupLabel>Application</Sidebar.GroupLabel>
          <Sidebar.GroupAction>
            <Plus /> <span className="sr-only">Add Project</span>
          </Sidebar.GroupAction>
          <Sidebar.GroupContent></Sidebar.GroupContent>
        </Sidebar.Group>
      </Sidebar.Content>
    </Sidebar.Root>
  )
}

Collapsible Sidebar.Group

To make a Sidebar.Group collapsible, wrap it in a Collapsible.

A collapsible sidebar group.

export function AppSidebar() {
  return (
    <Collapsible.Root defaultOpen className="group/collapsible">
      <Sidebar.Group>
        <Sidebar.GroupLabel render={
          <Collapsible.Trigger>
            Help
            <ChevronDown className="ml-auto transition-transform group-data-open/collapsible:rotate-180" />
          </Collapsible.Trigger>
        } />
        <CollapsibleContent>
          <Sidebar.GroupContent />
        </CollapsibleContent>
      </Sidebar.Group>
    </Collapsible.Root>
  )
}

Note: We wrap the Collapsible.Trigger in a Sidebar.GroupLabel to render a button.

SidebarGroupAction

Use the SidebarGroupAction component to add an action button to the SidebarGroup.

A sidebar group with an action button.

export function AppSidebar() {
  return (
    <Sidebar.Group>
      <Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
      <Sidebar.GroupAction title="Add Project">
        <Plus /> <span className="sr-only">Add Project</span>
      </Sidebar.GroupAction>
      <Sidebar.GroupContent />
    </Sidebar.Group>
  )
}

Sidebar.Menu

The Sidebar.Menu component is used for building a menu within a Sidebar.Group.

A Sidebar.Menu component is composed of Sidebar.MenuItem, Sidebar.MenuButton, <Sidebar.MenuAction /> and <Sidebar.MenuSub /> components.

Sidebar Menu

Here's an example of a Sidebar.Menu component rendering a list of projects.

A sidebar menu with a list of projects.

<Sidebar.Root>
  <Sidebar.Content>
    <Sidebar.Group>
      <Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
      <Sidebar.GroupContent>
        <Sidebar.Menu>
          {projects.map((project) => (
            <Sidebar.MenuItem key={project.name}>
              <Sidebar.MenuButton
                render={
                  <a href={project.url}>
                    <project.icon />
                    <span>{project.name}</span>
                  </a>
                }
              />
            </Sidebar.MenuItem>
          ))}
        </Sidebar.Menu>
      </Sidebar.GroupContent>
    </Sidebar.Group>
  </Sidebar.Content>
</Sidebar.Root>

Sidebar.MenuButton

The Sidebar.MenuButton component is used to render a menu button within a Sidebar.MenuItem.

By default, the Sidebar.MenuButton renders a button but you can use the render prop to render a different component such as a Link or an a tag.

<Sidebar.MenuButton render={<a href="#">Home</a>} />

Icon and Label

You can render an icon and a truncated label inside the button. Remember to wrap the label in a <span>.

<Sidebar.MenuButton
  render={
    <a href="#">
      <Home />
      <span>Home</span>
    </a>
  }
/>

isActive

Use the isActive prop to mark a menu item as active.

<Sidebar.MenuButton isActive render={<a href="#">Home</a>} />

Sidebar.MenuAction

The Sidebar.MenuAction component is used to render a menu action within a Sidebar.MenuItem.

This button works independently of the Sidebar.MenuButton i.e you can have the <Sidebar.MenuButton /> as a clickable link and the <Sidebar.MenuAction /> as a button.

<Sidebar.MenuItem>
  <Sidebar.MenuButton
    render={
      <a href="#">
        <Home />
        <span>Home</span>
      </a>
    }
  />
  <Sidebar.MenuAction>
    <Plus /> <span className="sr-only">Add Project</span>
  </Sidebar.MenuAction>
</Sidebar.MenuItem>

Here's an example of a Sidebar.MenuAction component rendering a Menu.

A sidebar menu action with a dropdown menu.

<Sidebar.MenuItem>
  <Sidebar.MenuButton
    className="group-has-data-popup-open/menu-item:bg-sidebar-accent"
    render={
      <a href="#">
        <Home />
        <span>Home</span>
      </a>
    }
  />
  <Menu.Root>
    <Menu.Trigger
      render={
        <Sidebar.MenuAction>
          <MoreHorizontalIcon />
          <span className="sr-only">More</span>
        </Sidebar.MenuAction>
      }
    />
    <Menu.Positioner align="start" side="right">
      <Menu.Popup>
        <Menu.Item>
          <span>Edit Project</span>
        </Menu.Item>
        <Menu.Item>
          <span>Delete Project</span>
        </Menu.Item>
      </Menu.Popup>
    </Menu.Positioner>
  </Menu.Root>
</Sidebar.MenuItem>

Sidebar.MenuSub

The Sidebar.MenuSub component is used to render a submenu within a Sidebar.Menu.

Use <Sidebar.MenuSubItem /> and <Sidebar.MenuSubButton /> to render a submenu item.

A sidebar menu with a submenu.

<Sidebar.MenuItem>
  <Sidebar.MenuButton />
  <Sidebar.MenuSub>
    <Sidebar.MenuSubItem>
      <Sidebar.MenuSubButton />
    </Sidebar.MenuSubItem>
    <Sidebar.MenuSubItem>
      <Sidebar.MenuSubButton />
    </Sidebar.MenuSubItem>
  </Sidebar.MenuSub>
</Sidebar.MenuItem>

Collapsible Sidebar.Menu

To make a Sidebar.Menu component collapsible, wrap it and the Sidebar.MenuSub components in a Collapsible.

A collapsible sidebar menu.

<Sidebar.Menu>
  <Collapsible.Root defaultOpen className="group/collapsible">
    <Sidebar.MenuItem>
      <Collapsible.Trigger render={<Sidebar.MenuButton />} />
      <Collapsible.Panel>
        <Sidebar.MenuSub>
          <Sidebar.MenuSubItem />
        </Sidebar.MenuSub>
      </Collapsible.Panel>
    </Sidebar.MenuItem>
  </Collapsible.Root>
</Sidebar.Menu>

Sidebar.MenuBadge

The Sidebar.MenuBadge component is used to render a badge within a Sidebar.MenuItem.

A sidebar menu with a badge.

<Sidebar.MenuItem>
  <Sidebar.MenuButton />
  <Sidebar.MenuBadge>24</Sidebar.MenuBadge>
</Sidebar.MenuItem>

Sidebar.MenuSkeleton

The Sidebar.MenuSkeleton component is used to render a skeleton for a Sidebar.Menu. You can use this to show a loading state when using React Server Components, SWR or react-query.

function NavProjectsSkeleton() {
  return (
    <Sidebar.Menu>
      {Array.from({ length: 5 }).map((_, index) => (
        <Sidebar.MenuItem key={index}>
          <Sidebar.MenuSkeleton />
        </Sidebar.MenuItem>
      ))}
    </Sidebar.Menu>
  )
}

Sidebar.Separator

The Sidebar.Separator component is used to render a separator within a Sidebar.Root.

<Sidebar.Root>
  <Sidebar.Header />
  <Sidebar.Separator />
  <Sidebar.Content>
    <Sidebar.Group />
    <Sidebar.Separator />
    <Sidebar.Group />
  </Sidebar.Content>
</Sidebar.Root>

Sidebar.Trigger

Use the Sidebar.Trigger component to render a button that toggles the sidebar.

The Sidebar.Trigger component must be used within a Sidebar.Provider.

<Sidebar.Provider>
  <Sidebar.Root />
  <main>
    <Sidebar.Trigger />
  </main>
</Sidebar.Provider>

Custom Trigger

To create a custom trigger, you can use the useSidebar hook.

import { useSidebar } from "@/components/caprice-ui/sidebar"

export function CustomTrigger() {
  const { toggleSidebar } = useSidebar()

  return <button onClick={toggleSidebar}>Toggle Sidebar</button>
}

Sidebar.Rail

The Sidebar.Rail component is used to render a rail within a Sidebar.Root. This rail can be used to toggle the sidebar.

<Sidebar.Root>
  <Sidebar.Header />
  <Sidebar.Content>
    <Sidebar.Group />
  </Sidebar.Content>
  <Sidebar.Footer />
  <Sidebar.Rail />
</Sidebar.Root>

Data Fetching

React Server Components

Here's an example of a Sidebar.Menu component rendering a list of projects using React Server Components.

A sidebar menu using React Server Components.

Skeleton to show loading state.
function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton showIcon />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
Server component fetching data.
async function NavProjects() {
  const projects = await fetchProjects()

  return (
    <Sidebar.Menu>
      {projects.map((project) => (
        <Sidebar.MenuItem key={project.name}>
          <Sidebar.MenuButton render={
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>} />
        </Sidebar.MenuItem>
      ))}
    </Sidebar.Menu>
  )
}
Usage with React Suspense.
function AppSidebar() {
  return (
    <Sidebar.Root>
      <Sidebar.Content>
        <Sidebar.Group>
          <Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
          <Sidebar.GroupContent>
            <React.Suspense fallback={<NavProjectsSkeleton />}>
              <NavProjects />
            </React.Suspense>
          </Sidebar.GroupContent>
        </Sidebar.Group>
      </Sidebar.Content>
    </Sidebar.Root>
  )
}

SWR and React Query

You can use the same approach with SWR or react-query.

SWR
function NavProjects() {
  const { data, isLoading } = useSWR("/api/projects", fetcher)

  if (isLoading) {
    return (
      <Sidebar.Menu>
        {Array.from({ length: 5 }).map((_, index) => (
          <Sidebar.MenuItem key={index}>
            <Sidebar.MenuSkeleton showIcon />
          </Sidebar.MenuItem>
        ))}
      </Sidebar.Menu>
    )
  }

  if (!data) {
    return ...
  }

  return (
    <Sidebar.Menu>
      {data.map((project) => (
        <Sidebar.MenuItem key={project.name}>
          <Sidebar.MenuButton render={
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          } />
        </Sidebar.MenuItem>
      ))}
    </Sidebar.Menu>
  )
}
React Query
function NavProjects() {
  const { data, isLoading } = useQuery()

  if (isLoading) {
    return (
      <Sidebar.Menu>
        {Array.from({ length: 5 }).map((_, index) => (
          <Sidebar.MenuItem key={index}>
            <Sidebar.MenuSkeleton showIcon />
          </Sidebar.MenuItem>
        ))}
      </Sidebar.Menu>
    )
  }

  if (!data) {
    return ...
  }

  return (
    <Sidebar.Menu>
      {data.map((project) => (
        <Sidebar.MenuItem key={project.name}>
          <Sidebar.MenuButton render={
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          } />
        </Sidebar.MenuItem>
      ))}
    </Sidebar.Menu>
  )
}

Controlled Sidebar

Use the open and onOpenChange props to control the sidebar.

A controlled sidebar.

export function AppSidebar() {
  const [open, setOpen] = React.useState(false)

  return (
    <Sidebar.Provider open={open} onOpenChange={setOpen}>
      <Sidebar.Root />
    </Sidebar.Provider>
  )
}

Theming

We use the following CSS variables to theme the sidebar.

@layer base {
  :root {
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }

  .dark {
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 0 0% 98%;
    --sidebar-primary-foreground: 240 5.9% 10%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }
}

We intentionally use different variables for the sidebar and the rest of the application to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application.

Styling

Here are some tips for styling the sidebar based on different states.

  • Styling an element based on the sidebar collapsible state. The following will hide the SidebarGroup when the sidebar is in icon mode.
<Sidebar.Root collapsible="icon">
  <Sidebar.Content>
    <Sidebar.Group className="group-data-[collapsible=icon]:hidden" />
  </Sidebar.Content>
</Sidebar.Root>
  • Styling a menu action based on the menu button active state. The following will force the menu action to be visible when the menu button is active.
<Sidebar.MenuItem>
  <Sidebar.MenuButton />
  <Sidebar.MenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
</Sidebar.MenuItem>

You can find more tips on using states for styling in this Twitter thread.