How does shadcn implement Toggle Layout feature?
I want to understand exactly how shadcn frontend is doing the feature to "Toggle Layout" to switch between contained / full screen layout in larger screens. There's a button to do it in larger breakpoints, At the left side of the theme toggle
I'll help you understand how the "Toggle Layout" feature works. Let me start by exploring the repository structure to find the relevant code.
Let me check the main website/app in the apps directory:
Great! I can see there's a use-layout.tsx hook. Let me also search for components that might handle the layout toggle:
Perfect! Let me check the site-config component:
Now let me find where the SiteConfig component is used and how it's displayed in the header:
Let me search more broadly:
Great! Let me check the site-header component to see how it's positioned:
Now let me check how the layout is actually applied at the root level. Let me look at the main layout and the global CSS:
Let me see the rest of the CSS, particularly how the layout classes are styled:
Let me continue reading the CSS to find the layout-specific styling:
Let me search for where the actual layout styling happens. Let me look at the tailwind config:
Let me check the components themselves that use the layout. Let me search for how the layout affects the page layout:
Perfect! Now I can see the pattern. Let me look at one more component to understand better how 3xl:fixed: works. Let me check if there's any tailwind config:
Let me check the tailwind-indicator component to see if it gives clues, and also look for where the custom variant is defined:
Excellent! Now I have all the pieces. Let me create a comprehensive summary document for the user. Let me also verify the understanding by looking at one more component to see how it's used in practice:
Perfect! Now I have a complete understanding. Let me provide a comprehensive explanation to the user:
##Summary: How shadcn-ui Implements "Toggle Layout" Feature
I've thoroughly explored the source code and can explain exactly how the layout toggle feature works. Here's the complete breakdown:
###1. The Toggle Button Component (site-config.tsx)
The button itself is simple and straightforward:
export function SiteConfig({ className }: React.ComponentProps<typeof Button>) {
const { layout, setLayout } = useLayout()
return (
<Button
variant="ghost"
size="icon"
onClick={() => {
const newLayout = layout === "fixed" ? "full" : "fixed"
setLayout(newLayout)
trackEvent({
name: "set_layout",
properties: { layout: newLayout },
})
}}
className={cn("size-8", className)}
title="Toggle layout"
>
<span className="sr-only">Toggle layout</span>
<GalleryHorizontalIcon />
</Button>
)
}
Key points:
- Uses the
useLayout()hook to get the current layout state - Toggles between
"fixed"and"full"layouts - Positioned in the header with
3xl:fixed:flex hidden(only visible at 3xl breakpoint) - Positioned to the left of the theme toggle (ModeSwitcher)
###2. The Layout Context & Hook (use-layout.tsx)
This is the core state management system:
State values:
"fixed"- Contained/fixed layout"full"- Full-screen layout (default)
How it works:
const Layout = ({
forcedLayout,
storageKey = "layout", // Persists to localStorage
defaultLayout = "full", // Default is full-screen
attribute = "class", // Applies to HTML element
value, // Optional custom class mapping
children,
}: LayoutProviderProps) => {
// 1. Reads from localStorage on mount
const [layout, setLayoutState] = React.useState<Layout>(() => {
if (isServer) return defaultLayout
const saved = localStorage.getItem(storageKey)
return (saved === "fixed" || saved === "full") ? saved : defaultLayout
})
// 2. Applies the layout to the HTML element
const applyLayout = React.useCallback((layout: Layout) => {
const name = value ? value[layout] : `layout-${layout}`
const d = document.documentElement
// Removes opposite class and adds current one
d.classList.remove(...attrs) // attrs = ["layout-fixed", "layout-full"]
if (name) d.classList.add(name)
}, [])
// 3. Persists changes and syncs across tabs
const setLayout = React.useCallback((value: Layout | ((prev: Layout) => Layout)) => {
// ... update state and save to localStorage
saveToLS(storageKey, newLayout)
}, [storageKey])
// Listen for storage events (other tabs changing layout)
React.useEffect(() => {
window.addEventListener("storage", handleStorage)
}, [])
// Apply on mount and changes
React.useEffect(() => {
applyLayout(forcedLayout ?? layout)
}, [forcedLayout, layout, applyLayout])
return (
<LayoutContext.Provider value={{layout, setLayout, forcedLayout}}>
{children}
</LayoutContext.Provider>
)
}
###3. Initial Layout Application (app/layout.tsx)
To prevent layout shift on hydration, the layout class is applied in a script that runs before React hydrates:
<script
dangerouslySetInnerHTML={{
__html: `
try {
// ... theme detection code ...
if (localStorage.layout) {
document.documentElement.classList.add('layout-' + localStorage.layout)
}
} catch (_) {}
`,
}}
/>
This ensures the correct layout class (layout-fixed or layout-full) is applied immediately, preventing a flash of unstyled content.
###4. CSS Custom Variant (styles/globals.css)
The magic happens with Tailwind's custom variant system:
@custom-variant fixed (&:is(.layout-fixed *));
This creates a fixed: modifier that matches styles only when the HTML element has the .layout-fixed class.
###5. How Components Use the Layout
With this variant, components can conditionally apply styles using the 3xl:fixed: prefix (3xl = 3xl breakpoint, fixed = the custom variant):
// In site-header.tsx
<div className="3xl:fixed:container flex h-(--header-height)">
{/* When at 3xl breakpoint AND layout is "fixed", apply "container" class */}
</div>
// In docs-sidebar.tsx
<SidebarProvider className="3xl:fixed:container 3xl:fixed:px-3 ...">
{/* Multiple utilities can use the fixed: variant */}
</SidebarProvider>
###6. What Actually Changes
The 3xl:fixed: utilities apply container constraints and padding when:
- Viewport is at 3xl breakpoint (1600px+)
- AND the layout is set to
"fixed"
This makes the content contained within a max-width container instead of spanning the full screen width.
Examples of controlled elements:
3xl:fixed:container- Applies max-width container3xl:fixed:px-0- Removes padding in fixed layout3xl:fixed:bg-none- Removes background3xl:fixed:max-w-48- Sets max widths
###Key Features
✅ Persisted: Saves to localStorage, persists across sessions
✅ Cross-tab sync: Uses storage events to sync between tabs
✅ No hydration mismatch: Applied before React hydrates
✅ Responsive: Only active at 3xl breakpoint and above
✅ Tracked: Analytics events capture layout changes
✅ Accessible: Button has ARIA label and semantic structure
The design is elegant: rather than changing the entire layout structure, it uses CSS variants to selectively apply layout utilities only when needed!
Can you show me exactly how it persist the layout state?
REPLY IN THREAD
Absolutely! Let me show you the exact persistence mechanism:
##Exact Persistence Flow
Here's exactly how the layout state is persisted:
###1. Save Function (Lines 24-29)
const saveToLS = (storageKey: string, value: string) => {
try {
localStorage.setItem(storageKey, value)
} catch {
// Unsupported - handles cases where localStorage is not available
}
}
Simple wrapper around localStorage.setItem() with error handling.
###2. Reading from Storage on Mount (Lines 48-56)
const [layout, setLayoutState] = React.useState<Layout>(() => {
if (isServer) return defaultLayout // SSR check
try {
const saved = localStorage.getItem(storageKey) // storageKey defaults to "layout"
if (saved === "fixed" || saved === "full") {
return saved // ✅ Restore saved layout
}
return defaultLayout // Fallback to default
} catch {
return defaultLayout // Fallback on error
}
})
On component mount, it immediately checks localStorage.getItem("layout") and restores the user's previous choice.
###3. Saving When Layout Changes (Lines 79-91)
When the user clicks the toggle button, it calls setLayout():
const setLayout = React.useCallback(
(value: Layout | ((prev: Layout) => Layout)) => {
if (typeof value === \"function\") {
setLayoutState((prevLayout) => {
const newLayout = value(prevLayout)
saveToLS(storageKey, newLayout) // ✅ SAVE TO localStorage
return newLayout
})
} else {
setLayoutState(value)
saveToLS(storageKey, value) // ✅ SAVE TO localStorage
}
},
[storageKey]
)
Two paths:
- Function: When you pass a function like
setLayout(prev => prev === "fixed" ? "full" : "fixed") - Direct value: When you pass a string like
setLayout("fixed")
Both paths call saveToLS() to persist to localStorage.
###4. Cross-Tab Sync (Lines 93-107)
React.useEffect(() => {
const handleStorage = (e: StorageEvent) => {
if (e.key !== storageKey) return // Only listen for "layout" key
if (!e.newValue) {
setLayout(defaultLayout) // If cleared, reset to default
} else if (e.newValue === \"fixed\" || e.newValue === \"full\") {
setLayoutState(e.newValue) // ✅ UPDATE STATE FROM OTHER TAB
}
}
window.addEventListener(\"storage\", handleStorage) // Browser storage event
return () => window.removeEventListener(\"storage\", handleStorage)
}, [setLayout, storageKey, defaultLayout])
This is the magic for cross-tab sync:
- When you toggle layout in one tab, it calls
localStorage.setItem("layout", newValue) - All OTHER tabs listening to the
"storage"event are notified - They update their React state immediately without a page reload
###5. Hydration Safety (Lines 121-125)
// Prevent layout changes during hydration
const [isHydrated, setIsHydrated] = React.useState(false)
React.useEffect(() => {
setIsHydrated(true)
})
The provider value only uses the restored layout after hydration completes to avoid mismatches between server and client.
##Complete Flow Example
User opens the site for the first time:
- Initial script in
layout.tsxruns before React hydrates - Script checks
localStorage.getItem("layout")- returnsnull - Falls back to default
"full"layout - React mounts and sets up the Layout provider
useLayoutState()initializer runs, checks localStorage again →"full"- Context value provided with
layout: "full"
User clicks toggle button:
SiteConfigcomponent callssetLayout("fixed")setLayout()callssaveToLS("layout", "fixed")localStorage.setItem("layout", "fixed")executed- React state updates to
"fixed" applyLayout("fixed")runs, addslayout-fixedclass to<html>- All
3xl:fixed:utilities now activate
User refreshes the page:
- Script in
layout.tsxruns before React loads - Script reads
localStorage.getItem("layout")→"fixed" - Script adds
layout-fixedclass immediately - No flash because styling is applied before content renders
- React mounts and finds the same layout state in localStorage
- Everything is consistent
User opens another tab:
- They toggle layout in Tab A →
localStorage.setItem("layout", "full") - Tab B receives
storageevent withe.key === "layout"ande.newValue === "full" - Tab B's
handleStorage()runs →setLayoutState("full") - Tab B's UI updates immediately without page reload
##Storage Key Details
The default storage key is "layout", which you can customize:
<LayoutProvider storageKey="my-custom-layout-key">
{children}
</LayoutProvider>
This stores data in localStorage as:
localStorage.layout = "fixed" // or "full"
END OF POST
Claude Haiku 4.5