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
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
##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