diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts
index 471a9c89158..e96ec0b2e5c 100644
--- a/apps/design-system/config/docs.ts
+++ b/apps/design-system/config/docs.ts
@@ -349,6 +349,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/slider',
items: [],
},
+ {
+ title: 'Sidebar',
+ href: '/docs/components/sidebar',
+ items: [],
+ },
{
title: 'Sonner',
href: '/docs/components/sonner',
diff --git a/apps/design-system/content/docs/components/sidebar.mdx b/apps/design-system/content/docs/components/sidebar.mdx
new file mode 100644
index 00000000000..f8706d0f4ae
--- /dev/null
+++ b/apps/design-system/content/docs/components/sidebar.mdx
@@ -0,0 +1,1372 @@
+---
+title: Sidebar
+description: A composable, themeable and customizable sidebar component.
+component: true
+---
+
+
+
+
+ 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.
+
+[Browse the Blocks Library](/blocks).
+
+## Installation
+
+
+
+
+ CLI
+ Manual
+
+
+
+
+
+Run the following command to install `sidebar.tsx`
+
+```bash
+npx shadcn@latest add sidebar
+```
+
+Add the following colors to your CSS file
+
+The command above should install the colors for you. If not, copy and paste the following in your CSS file.
+
+We'll go over the colors later in the [theming section](/docs/components/sidebar#theming).
+
+```css title="app/globals.css"
+@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: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --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%;
+ }
+}
+```
+
+
+
+
+
+
+
+
+
+Copy and paste the following code into your project.
+
+
+
+Update the import paths to match your project setup.
+
+Add the following colors to your CSS file
+
+We'll go over the colors later in the [theming section](/docs/components/sidebar#theming).
+
+```css title="app/globals.css"
+@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: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --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%;
+ }
+}
+```
+
+Add the Sidebar tailwind config into `tailwind.config.js`
+
+Add the following object in the `theme.extend.colors` section of your `tailwind.config.js` file
+this config enable sidebar related style utilities like bg-sidebar
+
+```javascript title="tailwind.config.js"
+// ...
+sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))',
+},
+// ...
+```
+
+
+
+
+
+
+
+## Structure
+
+A `Sidebar` component is composed of the following parts:
+
+- `SidebarProvider` - Handles collapsible state.
+- `Sidebar` - The sidebar container.
+- `SidebarHeader` and `SidebarFooter` - Sticky at the top and bottom of the sidebar.
+- `SidebarContent` - Scrollable content.
+- `SidebarGroup` - Section within the `SidebarContent`.
+- `SidebarTrigger` - Trigger for the `Sidebar`.
+
+
+
+
+## Usage
+
+```tsx showLineNumbers title="app/layout.tsx"
+import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
+import { AppSidebar } from '@/components/app-sidebar'
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
+```
+
+```tsx showLineNumbers title="components/app-sidebar.tsx"
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarHeader,
+} from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+## Your First Sidebar
+
+Let's start with the most basic sidebar. A collapsible sidebar with a menu.
+
+
+
+Add a `SidebarProvider` and `SidebarTrigger` at the root of your application.
+
+```tsx showLineNumbers title="app/layout.tsx"
+import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
+import { AppSidebar } from '@/components/app-sidebar'
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
+```
+
+Create a new sidebar component at `components/app-sidebar.tsx`.
+
+```tsx showLineNumbers title="components/app-sidebar.tsx"
+import { Sidebar, SidebarContent } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return (
+
+
+
+ )
+}
+```
+
+Now, let's add a `SidebarMenu` to the sidebar.
+
+We'll use the `SidebarMenu` component in a `SidebarGroup`.
+
+```tsx showLineNumbers title="components/app-sidebar.tsx"
+import { Calendar, Home, Inbox, Search, Settings } from 'lucide-react'
+
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+} from '@/components/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 (
+
+
+
+ Application
+
+
+ {items.map((item) => (
+
+
+
+
+ {item.title}
+
+
+
+ ))}
+
+
+
+
+
+ )
+}
+```
+
+You've created your first sidebar.
+
+
+
+ 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 components such as `DropdownMenu`, `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.
+
+## SidebarProvider
+
+The `SidebarProvider` component is used to provide the sidebar context to the `Sidebar` component. You should always wrap your application in a `SidebarProvider` component.
+
+### Props
+
+| Name | Type | Description |
+| -------------- | ------------------------- | -------------------------------------------- |
+| `defaultOpen` | `boolean` | Default open state of the sidebar. |
+| `open` | `boolean` | Open state of the sidebar (controlled). |
+| `onOpenChange` | `(open: boolean) => void` | Sets 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.
+
+```tsx showLineNumbers title="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.
+
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
+
+
+
+```
+
+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.
+
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
+```
+
+### Persisted State
+
+The `SidebarProvider` 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 `SidebarProvider` in `app/layout.tsx` like this:
+
+```tsx showLineNumbers title="app/layout.tsx"
+import { cookies } from 'next/headers'
+
+import { SidebarProvider, SidebarTrigger } from '@/components/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 (
+
+
+
+
+ {children}
+
+
+ )
+}
+```
+
+You can change the name of the cookie by updating the `SIDEBAR_COOKIE_NAME` variable in `sidebar.tsx`.
+
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
+const SIDEBAR_COOKIE_NAME = 'sidebar_state'
+```
+
+## Sidebar
+
+The main `Sidebar` component used to render a collapsible sidebar.
+
+```tsx showLineNumbers
+import { Sidebar } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return
+}
+```
+
+### Props
+
+| Property | Type | Description |
+| ------------- | --------------------------------- | --------------------------------- |
+| `side` | `left` or `right` | The side of the sidebar. |
+| `variant` | `sidebar`, `floating`, or `inset` | The variant of the sidebar. |
+| `collapsible` | `offcanvas`, `icon`, or `none` | Collapsible state of the sidebar. |
+
+### side
+
+Use the `side` prop to change the side of the sidebar.
+
+Available options are `left` and `right`.
+
+```tsx showLineNumbers
+import { Sidebar } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return
+}
+```
+
+### variant
+
+Use the `variant` prop to change the variant of the sidebar.
+
+Available options are `sidebar`, `floating` and `inset`.
+
+```tsx showLineNumbers
+import { Sidebar } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return
+}
+```
+
+
+ **Note:** If you use the `inset` variant, remember to wrap your main content in a `SidebarInset`
+ component.
+
+
+```tsx showLineNumbers
+
+
+
+ {children}
+
+
+```
+
+### collapsible
+
+Use the `collapsible` prop to make the sidebar collapsible.
+
+Available options are `offcanvas`, `icon` and `none`.
+
+```tsx showLineNumbers
+import { Sidebar } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return
+}
+```
+
+| Prop | Description |
+| ----------- | ------------------------------------------------------------ |
+| `offcanvas` | A collapsible sidebar that slides in from the left or right. |
+| `icon` | A sidebar that collapses to icons. |
+| `none` | A non-collapsible sidebar. |
+
+## useSidebar
+
+The `useSidebar` hook is used to control the sidebar.
+
+```tsx showLineNumbers
+import { useSidebar } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } = useSidebar()
+}
+```
+
+| Property | Type | Description |
+| --------------- | ------------------------- | --------------------------------------------- |
+| `state` | `expanded` or `collapsed` | The current state of the sidebar. |
+| `open` | `boolean` | Whether the sidebar is open. |
+| `setOpen` | `(open: boolean) => void` | Sets the open state of the sidebar. |
+| `openMobile` | `boolean` | Whether the sidebar is open on mobile. |
+| `setOpenMobile` | `(open: boolean) => void` | Sets the open state of the sidebar on mobile. |
+| `isMobile` | `boolean` | Whether the sidebar is on mobile. |
+| `toggleSidebar` | `() => void` | Toggles the sidebar. Desktop and mobile. |
+
+## SidebarHeader
+
+Use the `SidebarHeader` component to add a sticky header to the sidebar.
+
+The following example adds a `` to the `SidebarHeader`.
+
+
+
+
+ A sidebar header with a dropdown menu.
+
+
+
+```tsx showLineNumbers title="components/app-sidebar.tsx"
+
+
+
+
+
+
+
+ Select Workspace
+
+
+
+
+
+ Acme Inc
+
+
+ Acme Corp.
+
+
+
+
+
+
+
+```
+
+## SidebarFooter
+
+Use the `SidebarFooter` component to add a sticky footer to the sidebar.
+
+The following example adds a `` to the `SidebarFooter`.
+
+
+
+
+ A sidebar footer with a dropdown menu.
+
+
+
+```tsx showLineNumbers title="components/app-sidebar.tsx"
+export function AppSidebar() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ Username
+
+
+
+
+
+ Account
+
+
+ Billing
+
+
+ Sign out
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+## SidebarContent
+
+The `SidebarContent` component is used to wrap the content of the sidebar. This is where you add your `SidebarGroup` components. It is scrollable.
+
+```tsx showLineNumbers
+import { Sidebar, SidebarContent } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return (
+
+
+
+
+
+
+ )
+}
+```
+
+## SidebarGroup
+
+Use the `SidebarGroup` component to create a section within the sidebar.
+
+A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`.
+
+
+
+ A sidebar group.
+
+
+```tsx showLineNumbers
+import { Sidebar, SidebarContent, SidebarGroup } from '@/components/ui/sidebar'
+
+export function AppSidebar() {
+ return (
+
+
+
+ Application
+
+ Add Project
+
+
+
+
+
+ )
+}
+```
+
+## Collapsible SidebarGroup
+
+To make a `SidebarGroup` collapsible, wrap it in a `Collapsible`.
+
+
+
+
+ A collapsible sidebar group.
+
+
+
+```tsx showLineNumbers
+export function AppSidebar() {
+ return (
+
+
+
+
+ Help
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+
+ **Note:** We wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render a button.
+
+
+## SidebarGroupAction
+
+Use the `SidebarGroupAction` component to add an action button to the `SidebarGroup`.
+
+```tsx showLineNumbers {5-7}
+export function AppSidebar() {
+ return (
+
+ Projects
+
+ Add Project
+
+
+
+ )
+}
+```
+
+
+
+
+ A sidebar group with an action button.
+
+
+
+## SidebarMenu
+
+The `SidebarMenu` component is used for building a menu within a `SidebarGroup`.
+
+A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`, `` and `` components.
+
+
+
+
+Here's an example of a `SidebarMenu` component rendering a list of projects.
+
+
+
+
+ A sidebar menu with a list of projects.
+
+
+
+```tsx showLineNumbers
+
+
+
+ Projects
+
+
+ {projects.map((project) => (
+
+
+
+
+ {project.name}
+
+
+
+ ))}
+
+
+
+
+
+```
+
+## SidebarMenuButton
+
+The `SidebarMenuButton` component is used to render a menu button within a `SidebarMenuItem`.
+
+### Link or Anchor
+
+By default, the `SidebarMenuButton` renders a button but you can use the `asChild` prop to render a different component such as a `Link` or an `a` tag.
+
+```tsx showLineNumbers
+
+ Home
+
+```
+
+### Icon and Label
+
+You can render an icon and a truncated label inside the button. Remember to wrap the label in a ``.
+
+```tsx showLineNumbers
+
+
+
+ Home
+
+
+```
+
+### isActive
+
+Use the `isActive` prop to mark a menu item as active.
+
+```tsx showLineNumbers
+
+ Home
+
+```
+
+## SidebarMenuAction
+
+The `SidebarMenuAction` component is used to render a menu action within a `SidebarMenuItem`.
+
+This button works independently of the `SidebarMenuButton` i.e you can have the `` as a clickable link and the `` as a button.
+
+```tsx showLineNumbers
+
+
+
+
+ Home
+
+
+
+ Add Project
+
+
+```
+
+### DropdownMenu
+
+Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`.
+
+
+
+
+ A sidebar menu action with a dropdown menu.
+
+
+
+```tsx showLineNumbers
+
+
+
+
+ Home
+
+
+
+
+
+
+
+
+
+
+ Edit Project
+
+
+ Delete Project
+
+
+
+
+```
+
+## SidebarMenuSub
+
+The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu`.
+
+Use `` and `` to render a submenu item.
+
+
+
+
+ A sidebar menu with a submenu.
+
+
+
+```tsx showLineNumbers
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Collapsible SidebarMenu
+
+To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`.
+
+
+
+ A collapsible sidebar menu.
+
+
+```tsx showLineNumbers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## SidebarMenuBadge
+
+The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`.
+
+
+
+
+ A sidebar menu with a badge.
+
+
+
+```tsx showLineNumbers
+
+
+ 24
+
+```
+
+## SidebarMenuSkeleton
+
+The `SidebarMenuSkeleton` component is used to render a skeleton for a `SidebarMenu`. You can use this to show a loading state when using React Server Components, SWR or react-query.
+
+```tsx showLineNumbers
+function NavProjectsSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+ )
+}
+```
+
+## SidebarSeparator
+
+The `SidebarSeparator` component is used to render a separator within a `Sidebar`.
+
+```tsx showLineNumbers
+
+
+
+
+
+
+
+
+
+```
+
+## SidebarTrigger
+
+Use the `SidebarTrigger` component to render a button that toggles the sidebar.
+
+The `SidebarTrigger` component must be used within a `SidebarProvider`.
+
+```tsx showLineNumbers
+
+
+
+
+
+
+```
+
+### Custom Trigger
+
+To create a custom trigger, you can use the `useSidebar` hook.
+
+```tsx showLineNumbers
+import { useSidebar } from '@/components/ui/sidebar'
+
+export function CustomTrigger() {
+ const { toggleSidebar } = useSidebar()
+
+ return
+}
+```
+
+## SidebarRail
+
+The `SidebarRail` component is used to render a rail within a `Sidebar`. This rail can be used to toggle the sidebar.
+
+```tsx showLineNumbers
+
+
+
+
+
+
+
+
+```
+
+## Data Fetching
+
+### React Server Components
+
+Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components.
+
+
+
+
+ A sidebar menu using React Server Components.
+
+
+
+```tsx showLineNumbers {6} title="Skeleton to show loading state."
+function NavProjectsSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+ )
+}
+```
+
+```tsx showLineNumbers {2} title="Server component fetching data."
+async function NavProjects() {
+ const projects = await fetchProjects()
+
+ return (
+
+ {projects.map((project) => (
+
+
+
+
+ {project.name}
+
+
+
+ ))}
+
+ )
+}
+```
+
+```tsx showLineNumbers {8-10} title="Usage with React Suspense."
+function AppSidebar() {
+ return (
+
+
+
+ Projects
+
+ }>
+
+
+
+
+
+
+ )
+}
+```
+
+### SWR and React Query
+
+You can use the same approach with [SWR](https://swr.vercel.app/) or [react-query](https://tanstack.com/query/latest/docs/framework/react/overview).
+
+```tsx showLineNumbers title="SWR"
+function NavProjects() {
+ const { data, isLoading } = useSWR("/api/projects", fetcher)
+
+ if (isLoading) {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+ )
+ }
+
+ if (!data) {
+ return ...
+ }
+
+ return (
+
+ {data.map((project) => (
+
+
+
+
+ {project.name}
+
+
+
+ ))}
+
+ )
+}
+```
+
+```tsx showLineNumbers title="React Query"
+function NavProjects() {
+ const { data, isLoading } = useQuery()
+
+ if (isLoading) {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+ )
+ }
+
+ if (!data) {
+ return ...
+ }
+
+ return (
+
+ {data.map((project) => (
+
+
+
+
+ {project.name}
+
+
+
+ ))}
+
+ )
+}
+```
+
+## Controlled Sidebar
+
+Use the `open` and `onOpenChange` props to control the sidebar.
+
+
+
+ A controlled sidebar.
+
+
+```tsx showLineNumbers
+export function AppSidebar() {
+ const [open, setOpen] = React.useState(false)
+
+ return (
+
+
+
+ )
+}
+```
+
+## Theming
+
+We use the following CSS variables to theme the sidebar.
+
+```css
+@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.
+
+```tsx
+
+
+
+
+
+```
+
+- **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.
+
+```tsx
+
+
+
+
+```
+
+You can find more tips on using states for styling in this [Twitter thread](https://x.com/shadcn/status/1842329158879420864).
+
+## Changelog
+
+### 2024-10-30 Cookie handling in setOpen
+
+- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in ``.
+
+Update the `setOpen` callback in `` as follows:
+
+```tsx showLineNumbers
+const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === 'function' ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+)
+```
+
+### 2024-10-21 Fixed `text-sidebar-foreground`
+
+- [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `` to `` component.
+
+### 2024-10-20 Typo in `useSidebar` hook.
+
+Fixed typo in `useSidebar` hook.
+
+```diff showLineNumbers title="sidebar.tsx"
+- throw new Error("useSidebar must be used within a Sidebar.")
++ throw new Error("useSidebar must be used within a SidebarProvider.")
+```
diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx
index 166fe56b8d7..37bf296a2ae 100644
--- a/packages/ui/index.tsx
+++ b/packages/ui/index.tsx
@@ -250,6 +250,7 @@ export * from './src/components/radio-group-card'
export * from './src/components/TreeView'
export * from './src/components/shadcn/ui/chart'
+export * from './src/components/shadcn/ui/sidebar'
// links
diff --git a/packages/ui/src/components/shadcn/hooks/useMobile.ts b/packages/ui/src/components/shadcn/hooks/useMobile.ts
new file mode 100644
index 00000000000..4331d5c5620
--- /dev/null
+++ b/packages/ui/src/components/shadcn/hooks/useMobile.ts
@@ -0,0 +1,19 @@
+import * as React from 'react'
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener('change', onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener('change', onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/packages/ui/src/components/shadcn/ui/sidebar.tsx b/packages/ui/src/components/shadcn/ui/sidebar.tsx
new file mode 100644
index 00000000000..77aea8b0612
--- /dev/null
+++ b/packages/ui/src/components/shadcn/ui/sidebar.tsx
@@ -0,0 +1,740 @@
+'use client'
+
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { VariantProps, cva } from 'class-variance-authority'
+import { PanelLeft } from 'lucide-react'
+
+import { useIsMobile } from '../hooks/useMobile'
+import { cn } from './../../../lib/utils'
+import { Button } from './button'
+import { Input } from './input'
+import { Separator } from './separator'
+import { Sheet, SheetContent } from './sheet'
+import { Skeleton } from './skeleton'
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'
+
+const SIDEBAR_COOKIE_NAME = 'sidebar:state'
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = '13rem'
+const SIDEBAR_WIDTH_MOBILE = '18rem'
+const SIDEBAR_WIDTH_ICON = '3rem'
+const SIDEBAR_KEYBOARD_SHORTCUT = '['
+
+type SidebarContext = {
+ state: 'expanded' | 'collapsed'
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error('useSidebar must be used within a SidebarProvider.')
+ }
+
+ return context
+}
+
+const SidebarProvider = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<'div'> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ }
+>(
+ (
+ {
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === 'function' ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+ return () => window.removeEventListener('keydown', handleKeyDown)
+ }, [toggleSidebar])
+
+ // 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 = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+