
Shadcn Layouts
- 442 installs
- 133 repo stars
- Updated February 24, 2026
- jwynia/agent-skills
shadcn-layouts is an agent skill that scaffolds responsive shadcn/ui app shells for developers who need dashboard, sidebar, header, and marketing page layouts without hand-rolling Tailwind grid and height chains.
About
shadcn-layouts is version 1.0 MIT utility skill in jwynia/agent-skills that generates correct shadcn and Tailwind layouts by applying CSS mental models agents usually miss. It targets React and Next.js projects using shadcn/ui with Tailwind CSS v3 or v4, focusing on viewport-filling app shells, dashboard sidebars, marketing pages, flex column scroll regions, and grid collapse fixes rather than syntax errors alone. The skill inspects height constraint chains, recommends utilities such as h-full, h-screen, min-h-0, min-w-0, shrink-0, and grid classes at the correct parent levels, and checks missing component dependencies or Tailwind configuration issues. Trigger phrases include create a shadcn layout, fix layout issues, debug CSS height problems, make scrolling work, and Tailwind flex or grid failures. Developers reach for shadcn-layouts when generated UI collapses, nested scroll areas fail, or h-full elements shrink inside flex columns despite otherwise valid shadcn components. Install via npx playbooks add skill jwynia/agent-skills --skill shadcn-layouts or equivalent skills CLI flows.
- shadcn/ui layout recipes
- dashboard and sidebar shells
- responsive grid patterns
- reusable section scaffolding
- faster consistent UI structure
Shadcn Layouts by the numbers
- 442 all-time installs (skills.sh)
- +3 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #644 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jwynia/agent-skills --skill shadcn-layoutsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 442 |
|---|---|
| repo stars | ★ 133 |
| Last updated | February 24, 2026 |
| Repository | jwynia/agent-skills ↗ |
How do you fix shadcn Tailwind layout height bugs?
Scaffold responsive app shells, dashboards, and marketing pages with shadcn/ui layout primitives instead of hand-rolling grid, sidebar, and header patterns from scratch.
Who is it for?
React and Next.js developers using shadcn/ui who need agents to fix flex height chains and scaffold dashboard or marketing layouts.
Skip if: Teams not using shadcn/ui or Tailwind for layout should skip shadcn-layouts.
When should I use this skill?
User asks to create shadcn layouts, fix collapsed h-full elements, debug scroll overflow, or repair Tailwind flex/grid dashboard shells.
What you get
Responsive shadcn layout components, corrected Tailwind utility chains, and scrollable app-shell or dashboard page structures.
- Responsive shadcn layout scaffolding
- Corrected Tailwind utility classes
- Scrollable dashboard or marketing shells
By the numbers
- Published as version 1.0 MIT skill in jwynia/agent-skills metadata
- Targets Tailwind CSS v3/v4 with shadcn/ui on React and Next.js
Files
shadcn/Tailwind Layouts
Help generate shadcn/Tailwind components that render correctly the first time. Most agent-generated UI fails due to missing mental models about how CSS layout works—not syntax errors, but assumption gaps.
When to Use This Skill
Use this skill when:
- Creating shadcn/Tailwind layouts
- Debugging height/scroll issues
- Fixing flex/grid problems
- Setting up full-page app shells
Do NOT use this skill when:
- Writing backend code
- Working on non-Tailwind CSS projects
- Designing (use frontend-design first)
Core Principle
CSS layout flows from constraints. Height flows down from explicit ancestors. Width flows up from content. Agents fail because they apply classes without understanding the constraint chain.
Critical Mental Models
Model 1: Height Inheritance Chain
h-full means height: 100%. 100% of what? 100% of the parent's computed height.
BROKEN (chain incomplete):
<html> <!-- no height -->
<body> <!-- no height -->
<div class="h-full"> <!-- 100% of nothing = 0 -->
WORKING (chain complete):
<html class="h-full"> <!-- 100% of viewport -->
<body class="h-full"> <!-- 100% of html -->
<div class="h-full"> <!-- 100% of body = works -->Rule: Trace from element up to <html>. Every ancestor needs explicit height, OR use viewport units (h-screen) to break the chain.
Model 2: Flex Overflow Gotcha
Flex children have implicit min-height: auto, preventing shrinking below content size.
// BROKEN (won't scroll)
<div className="flex flex-col h-screen">
<main className="flex-1 overflow-y-auto"> {/* Can't shrink! */}
// WORKING (scrolls correctly)
<div className="flex flex-col h-screen">
<main className="flex-1 overflow-y-auto min-h-0"> {/* Can shrink */}Rule: Flex children that scroll need min-h-0. Children that shouldn't shrink need shrink-0.
Model 3: Grid Parent/Child Separation
Grid is defined on the parent. Children just occupy cells.
// BROKEN
<div className="grid-cols-3"> {/* Missing 'grid'! */}
// WORKING
<div className="grid grid-cols-3"> {/* 'grid' enables grid-cols-* */}Rule: flex or grid must be declared on parent before direction/template classes work.
Model 4: Scroll Container Dimensions
Scroll containers need explicit dimensions to know when to scroll.
// BROKEN (never scrolls)
<ScrollArea> {/* No height constraint */}
// WORKING (flex-constrained)
<div className="flex flex-col h-screen">
<ScrollArea className="flex-1 min-h-0">Diagnostic States
SL1: Height Chain Broken
Symptoms: Elements collapse, h-full not working Fix: Trace to html, add heights or use h-screen
SL2: Flex Overflow Blocked
Symptoms: Scroll doesn't work, content overflows Fix: Add min-h-0 to flex children that scroll
SL3: Grid Structure Wrong
Symptoms: Items stack vertically instead of columns Fix: Ensure grid grid-cols-* on parent
SL4: Styles Not Applying
Symptoms: Unstyled components, colors wrong Fix: Check Tailwind content paths, CSS variables in globals.css
SL5: Component Dependencies Missing
Symptoms: "Module not found", functionality broken Fix: npx shadcn add [component], install peer deps
Common Layout Patterns
Full-Page App Shell
// layout.tsx
<html lang="en" className="h-full">
<body className="h-full">{children}</body>
</html>
// page.tsx
<div className="flex h-full">
<aside className="w-64 shrink-0 border-r overflow-y-auto">
<nav>...</nav>
</aside>
<main className="flex-1 min-w-0 overflow-y-auto">
{children}
</main>
</div>Dashboard with Header
<div className="flex flex-col h-screen">
<header className="h-16 shrink-0 border-b">...</header>
<div className="flex flex-1 min-h-0">
<aside className="w-64 shrink-0 border-r overflow-y-auto">...</aside>
<main className="flex-1 min-w-0 overflow-y-auto p-6">
{children}
</main>
</div>
</div>Card Grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map(item => (
<Card key={item.id}>
<CardHeader><CardTitle>{item.title}</CardTitle></CardHeader>
<CardContent>{item.content}</CardContent>
</Card>
))}
</div>Anti-Patterns
The Height Assumption
Using h-full without verifying ancestor chain. Fix: Trace to html. Use h-screen to break chain.
The Overflow Ignorance
Adding overflow-y-auto without min-h-0 on flex children. Fix: Flex children need min-h-0 to shrink.
The Import Guess
Guessing import paths like shadcn/ui. Fix: Check components.json for alias. Usually @/components/ui/*.
The Flat Compound
Flattening compound components (Dialog without DialogTrigger/DialogContent). Fix: Maintain required nesting structure.
Pre-Generation Checklist
- [ ] Import alias known (
@/components/ui/*) - [ ] html has
h-full - [ ] body has
h-fullormin-h-full - [ ] Scroll containers have explicit height
- [ ] Flex scroll children have
min-h-0 - [ ] Fixed elements have
shrink-0
Related Skills
- frontend-design - Design decisions before implementation
- react-pwa - PWA features for React apps
Component Dependencies & Setup Checklist
Quick reference for what each shadcn component requires to work correctly.
Installation Commands
# Install a single component
npx shadcn@latest add button
# Install multiple components
npx shadcn@latest add button card dialog
# Install all components (use sparingly)
npx shadcn@latest add --all---
Component Requirements Matrix
Basic Components (No Special Dependencies)
| Component | Install | Providers | Peer Deps |
|---|---|---|---|
| Button | npx shadcn add button | None | None |
| Card | npx shadcn add card | None | None |
| Badge | npx shadcn add badge | None | None |
| Input | npx shadcn add input | None | None |
| Label | npx shadcn add label | None | None |
| Separator | npx shadcn add separator | None | None |
| Skeleton | npx shadcn add skeleton | None | None |
| Avatar | npx shadcn add avatar | None | None |
| Aspect Ratio | npx shadcn add aspect-ratio | None | None |
Components Requiring Providers
| Component | Install | Provider Required | Setup |
|---|---|---|---|
| Tooltip | npx shadcn add tooltip | TooltipProvider | Wrap app in layout |
| Theme Toggle | npx shadcn add + manual | ThemeProvider | Install next-themes |
| Sonner (Toast) | npx shadcn add sonner | <Toaster /> | Add to layout |
TooltipProvider Setup:
// app/layout.tsx
import { TooltipProvider } from "@/components/ui/tooltip"
export default function RootLayout({ children }) {
return (
<html>
<body>
<TooltipProvider>
{children}
</TooltipProvider>
</body>
</html>
)
}ThemeProvider Setup:
npm install next-themes// components/theme-provider.tsx
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
>
{children}
</ThemeProvider>
</body>
</html>
)
}Sonner Setup:
// app/layout.tsx
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster />
</body>
</html>
)
}
// Usage anywhere:
import { toast } from "sonner"
toast("Event created")
toast.success("Success!")
toast.error("Error occurred")Components Requiring Peer Dependencies
| Component | Install | Peer Dependencies | Install Command |
|---|---|---|---|
| Form | npx shadcn add form | react-hook-form, zod | npm i react-hook-form @hookform/resolvers zod |
| Data Table | npx shadcn add table | @tanstack/react-table | npm i @tanstack/react-table |
| Charts | npx shadcn add chart | recharts | npm i recharts |
| Calendar | npx shadcn add calendar | react-day-picker, date-fns | npm i react-day-picker date-fns |
| Date Picker | npx shadcn add calendar popover button | react-day-picker, date-fns | npm i react-day-picker date-fns |
| Carousel | npx shadcn add carousel | embla-carousel-react | npm i embla-carousel-react |
| Drawer | npx shadcn add drawer | vaul | npm i vaul |
---
Compound Component Structures
Components that require specific nesting to work:
Dialog
<Dialog>
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Title</DialogTitle> {/* Required for a11y */}
<DialogDescription>Description</DialogDescription>
</DialogHeader>
{/* Content */}
<DialogFooter>
<Button>Action</Button>
</DialogFooter>
</DialogContent>
</Dialog>Required subcomponents: DialogTrigger, DialogContent, DialogTitle
AlertDialog
<AlertDialog>
<AlertDialogTrigger asChild>
<Button>Delete</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Required subcomponents: AlertDialogTrigger, AlertDialogContent, AlertDialogTitle, AlertDialogAction, AlertDialogCancel
Sheet
<Sheet>
<SheetTrigger asChild>
<Button>Open</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Title</SheetTitle> {/* Required for a11y */}
<SheetDescription>Description</SheetDescription>
</SheetHeader>
{/* Content */}
<SheetFooter>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>Required subcomponents: SheetTrigger, SheetContent, SheetTitle
DropdownMenu
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Logout</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Required subcomponents: DropdownMenuTrigger, DropdownMenuContent
Select
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>Required subcomponents: SelectTrigger, SelectValue, SelectContent, SelectItem
Tabs
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Content 1</TabsContent>
<TabsContent value="tab2">Content 2</TabsContent>
</Tabs>Required subcomponents: TabsList, TabsTrigger, TabsContent
Accordion
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content 2</AccordionContent>
</AccordionItem>
</Accordion>Required subcomponents: AccordionItem, AccordionTrigger, AccordionContent
Popover
<Popover>
<PopoverTrigger asChild>
<Button>Open</Button>
</PopoverTrigger>
<PopoverContent>
{/* Popover content */}
</PopoverContent>
</Popover>Required subcomponents: PopoverTrigger, PopoverContent
Command (Command Palette)
<Command>
<CommandInput placeholder="Search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search</CommandItem>
</CommandGroup>
</CommandList>
</Command>Required subcomponents: CommandInput, CommandList
Sidebar (shadcn sidebar component)
<SidebarProvider>
<Sidebar>
<SidebarHeader>...</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Menu</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">Home</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>...</SidebarFooter>
</Sidebar>
<main>{children}</main>
</SidebarProvider>Required: Must be wrapped in SidebarProvider
---
Form Component Setup
Full form setup with react-hook-form and zod:
npm i react-hook-form @hookform/resolvers zod
npx shadcn add form input button"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email.",
}),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="johndoe" {...field} />
</FormControl>
<FormDescription>
Your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="john@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}---
Data Table Setup
Full data table with TanStack Table:
npm i @tanstack/react-table
npx shadcn add table"use client"
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
getSortedRowModel,
SortingState,
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { useState } from "react"
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: {
sorting,
},
})
return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}---
Common Import Patterns
Based on typical components.json setup:
// UI Components
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
// Form Components
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
// Dialog/Modal
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
// Utility
import { cn } from "@/lib/utils"---
Checking What's Installed
# List installed components
ls -la components/ui/
# Check components.json for config
cat components.jsoncomponents.json structure:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}Layout Templates Reference
Copy-paste starting points for common layout patterns.
Full-Page Layouts
Basic App Shell (Sidebar + Content)
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="h-full">
<body className="h-full">{children}</body>
</html>
)
}
// app/(dashboard)/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex h-full">
{/* Fixed sidebar */}
<aside className="w-64 shrink-0 border-r bg-muted/40 overflow-y-auto">
<div className="p-4">
<h2 className="font-semibold">Navigation</h2>
<nav className="mt-4 space-y-2">
{/* Nav items */}
</nav>
</div>
</aside>
{/* Scrollable main */}
<main className="flex-1 min-w-0 overflow-y-auto">
<div className="p-6">
{children}
</div>
</main>
</div>
)
}Dashboard with Header + Sidebar
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex flex-col h-screen">
{/* Fixed header */}
<header className="h-14 shrink-0 border-b bg-background">
<div className="flex h-full items-center px-4">
<h1 className="font-semibold">App Name</h1>
<div className="ml-auto flex items-center gap-4">
{/* Header actions */}
</div>
</div>
</header>
{/* Body */}
<div className="flex flex-1 min-h-0">
{/* Sidebar */}
<aside className="w-64 shrink-0 border-r overflow-y-auto">
<nav className="p-4 space-y-2">
{/* Nav items */}
</nav>
</aside>
{/* Main content */}
<main className="flex-1 min-w-0 overflow-y-auto">
<div className="p-6">
{children}
</div>
</main>
</div>
</div>
)
}Collapsible Sidebar (with shadcn Sidebar)
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<SidebarProvider>
<div className="flex h-screen w-full">
<Sidebar>
<SidebarHeader>
<h2 className="font-semibold px-4 py-2">App Name</h2>
</SidebarHeader>
<SidebarContent>
{/* Navigation */}
</SidebarContent>
<SidebarFooter>
{/* Footer content */}
</SidebarFooter>
</Sidebar>
<main className="flex-1 min-w-0 overflow-y-auto">
<div className="flex items-center gap-2 p-4 border-b">
<SidebarTrigger />
<h1>Page Title</h1>
</div>
<div className="p-6">
{children}
</div>
</main>
</div>
</SidebarProvider>
)
}---
Split Panels
Two-Column Resizable
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable"
export default function SplitView() {
return (
<ResizablePanelGroup
direction="horizontal"
className="h-full rounded-lg border"
>
<ResizablePanel defaultSize={30} minSize={20}>
<div className="h-full overflow-y-auto p-4">
<h3 className="font-semibold mb-4">Left Panel</h3>
{/* Content */}
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={70}>
<div className="h-full overflow-y-auto p-4">
<h3 className="font-semibold mb-4">Right Panel</h3>
{/* Content */}
</div>
</ResizablePanel>
</ResizablePanelGroup>
)
}Three-Column (Sidebar + Content + Details)
<ResizablePanelGroup direction="horizontal" className="h-full">
<ResizablePanel defaultSize={20} minSize={15}>
<div className="h-full overflow-y-auto p-4">
{/* Navigation/list */}
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={50}>
<div className="h-full overflow-y-auto p-4">
{/* Main content */}
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={30} minSize={20}>
<div className="h-full overflow-y-auto p-4">
{/* Details panel */}
</div>
</ResizablePanel>
</ResizablePanelGroup>---
Content Layouts
Card Grid (Responsive)
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{items.map((item) => (
<Card key={item.id}>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
<CardDescription>{item.description}</CardDescription>
</CardHeader>
<CardContent>
{item.content}
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full">
View
</Button>
</CardFooter>
</Card>
))}
</div>List with Actions
<div className="divide-y rounded-lg border">
{items.map((item) => (
<div
key={item.id}
className="flex items-center justify-between p-4 hover:bg-muted/50"
>
<div className="flex items-center gap-4">
<Avatar>
<AvatarImage src={item.avatar} />
<AvatarFallback>{item.initials}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-muted-foreground">{item.email}</p>
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
))}
</div>Page with Tabs
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Settings</h1>
<p className="text-muted-foreground">
Manage your account settings
</p>
</div>
<Tabs defaultValue="general" className="space-y-4">
<TabsList>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<TabsContent value="general" className="space-y-4">
{/* General settings content */}
</TabsContent>
<TabsContent value="security" className="space-y-4">
{/* Security settings content */}
</TabsContent>
<TabsContent value="notifications" className="space-y-4">
{/* Notification settings content */}
</TabsContent>
</Tabs>
</div>---
Form Layouts
Single Column Form
<Card className="max-w-md">
<CardHeader>
<CardTitle>Create Account</CardTitle>
<CardDescription>Enter your details below</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="john@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Submit
</Button>
</form>
</Form>
</CardContent>
</Card>Two Column Form
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Full width field */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-4">
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit">Save</Button>
</div>
</form>
</Form>---
Modal/Dialog Layouts
Form Dialog
<Dialog>
<DialogTrigger asChild>
<Button>Add Item</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Add New Item</DialogTitle>
<DialogDescription>
Fill in the details below to add a new item.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="submit">Add Item</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>Confirmation Dialog
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete
the item from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete}>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Sheet (Side Panel)
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open Settings</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Settings</SheetTitle>
<SheetDescription>
Configure your preferences here.
</SheetDescription>
</SheetHeader>
<div className="py-4 space-y-4">
{/* Settings content */}
</div>
<SheetFooter>
<Button type="submit">Save changes</Button>
</SheetFooter>
</SheetContent>
</Sheet>---
Table Layouts
Basic Data Table
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row) => (
<TableRow key={row.id}>
<TableCell className="font-medium">{row.id}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>
<Badge variant={row.status === 'active' ? 'default' : 'secondary'}>
{row.status}
</Badge>
</TableCell>
<TableCell className="text-right">{row.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>Table with Scroll (Fixed Height)
<div className="rounded-md border">
{/* Fixed header */}
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
</Table>
{/* Scrollable body */}
<div className="max-h-[400px] overflow-y-auto">
<Table>
<TableBody>
{data.map((row) => (
<TableRow key={row.id}>
<TableCell className="w-[100px]">{row.id}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>{row.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>---
Critical Classes Quick Reference
| Purpose | Classes |
|---|---|
| Full viewport height | h-screen or h-dvh (mobile-safe) |
| Full parent height (chain required) | h-full |
| Minimum full height | min-h-screen or min-h-full |
| Flex child that scrolls | flex-1 min-h-0 overflow-y-auto |
| Flex child that doesn't shrink | shrink-0 |
| Prevent text overflow pushing width | min-w-0 |
| Basic grid | grid grid-cols-{n} gap-{n} |
| Responsive grid | grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 |
Related skills
How it compares
Pick shadcn-layouts over generic frontend-design skills when the bug is Tailwind height chains in shadcn dashboard shells, not branding or color systems.
FAQ
What layout problems does shadcn-layouts solve?
shadcn-layouts solves collapsed h-full elements, broken nested scrolling, flex and grid overflow issues, and missing parent height constraints when agents generate shadcn/ui and Tailwind dashboard or marketing layouts.
Which stacks does shadcn-layouts support?
shadcn-layouts supports shadcn/ui with Tailwind CSS v3 or v4 on React and Next.js projects, applying CSS mental models to layout primitives instead of only component syntax.
When should agents load shadcn-layouts?
Agents should load shadcn-layouts when users request shadcn layout scaffolding, height or scroll debugging, full-page app shells, or fixes for Tailwind flex and grid failures in dashboard UIs.