React
A layout with the shadcn sidebar
SidebarProvider wraps the sidebar and the content, a sidebar_state cookie prevents the opening jump, and the icon variant needs tooltips and hidden text.
Key points
- `SidebarProvider` must wrap both the sidebar and `SidebarInset`: the context holds state, width and shortcut.
- Read the `sidebar_state` cookie on the server and pass it to `defaultOpen`, otherwise the sidebar shifts after hydration.
- With `collapsible="icon"` every entry loses its label: it needs `tooltip` plus screen reader text.
Short answer: npx shadcn@latest add sidebar installs a set of components, not one file. Wrap the sidebar and the content in SidebarProvider, put the content in SidebarInset, and read the sidebar_state cookie on the server to avoid the jump on first paint.
The minimum structure
The layout has three layers: the provider that holds state, the sidebar that draws the menu, the inset that takes the remaining space. When the provider is missing, useSidebar() throws an explicit error: that is the first thing to check when the sidebar refuses to close.
// app/layout.tsx
import { cookies } from "next/headers";
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/app-sidebar";
export default async function Layout({ children }: { children: React.ReactNode }) {
const cookieStore = await cookies();
const defaultOpen = cookieStore.get("sidebar_state")?.value !== "false";
return (
Dashboard
{children}
);
}
The provider writes the sidebar_state cookie when the state changes. Reading it on the server and passing it to defaultOpen is what prevents the jump: without it, the first render uses the default and the sidebar moves after hydration.
The sidebar itself
// components/app-sidebar.tsx
import { Home, Settings } from "lucide-react";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
const items = [
{ title: "Panoramica", href: "/", icon: Home },
{ title: "Impostazioni", href: "/settings", icon: Settings },
];
export function AppSidebar() {
return (
Applicazione
{items.map((item) => (
{item.title}
))}
);
}
The three variants
| collapsible | Behaviour | When to use it |
|---|---|---|
| offcanvas | The sidebar leaves the screen and hands all the space to the content. | Applications with wide tables and a secondary menu. |
| icon | An icon rail 3rem wide stays; tooltips show the labels. | Dashboards with many sections and expert users. |
| none | No reduction: the sidebar is always visible. | Public pages with few links. |
The width is not a fixed number in the CSS: the provider exposes --sidebar-width and --sidebar-width-icon, and the icon variant reads the second. Changing them in :root is enough, without touching the components. The method matches the one in how to theme shadcn/ui.
Accessibility and mobile
Below the md breakpoint the sidebar turns into a Sheet: it opens over the content with a close button. Two details slip through. First: with the icon variant the labels disappear, so tooltip is not enough and the link needs text for the screen reader. Second: the Ctrl+B or ⌘B shortcut is already registered by the provider; do not add a second one.
A named limitation
The sidebar uses position: fixed and h-svh. An ancestor with transform, filter or overflow: hidden pins it to that container instead of the window, and the result is a bar that scrolls with the page. The catalogue, measured on 1 November 2026, holds 22 blocks in the sidebar category: almost all assume a full-height layout, not a page inside a card.
Where to continue
In the catalogue you find a sidebar block with preview, the sidebar category and the sidebar component record. For the theme read what shadcn/ui is and how to theme shadcn/ui; the block selection is in the best sidebar blocks of 2026.
Frequently asked questions
Why does useSidebar throw an error?
Because the component calling it is not inside SidebarProvider. The provider must wrap both the sidebar and SidebarInset, not just one of them.
How do I keep the sidebar open between visits?
The provider already writes the sidebar_state cookie. Read it in the server layout and pass it to defaultOpen; without that step the sidebar starts from the default and moves after hydration.
Why does the sidebar scroll with the page?
Because an ancestor has transform, filter or overflow: hidden: position fixed then refers to that container, not the window. Remove the property from the wrapper and the sidebar becomes fixed again.
How do I change the sidebar width?
Edit --sidebar-width for the open state and --sidebar-width-icon for the icon variant. They are CSS variables exposed by the provider, so the components stay untouched.
Does the sidebar work on mobile?
Yes. Below the md breakpoint it becomes a Sheet with an overlay and a close button. With the icon variant, check that every entry has an accessible label, because the visible text disappears.