用途
shadcn-layouts 用于帮助生成能一次正确渲染的 shadcn/Tailwind 组件。多数 Agent 生成的 UI 之所以失败,并非语法错误,而是缺少关于 CSS 布局工作方式的心智模型,存在假设缺口。
适用场景:
- 创建 shadcn/Tailwind 布局
- 调试高度/滚动问题
- 修复 flex/grid 问题
- 搭建整页应用外壳
不适用场景:
- 编写后端代码
- 非 Tailwind CSS 项目
- 设计工作(应先使用 frontend-design)
核心原则
CSS 布局由约束驱动。高度从显式的祖先元素向下传递,宽度由内容向上撑开。Agent 失败的原因在于只套用 class 而不理解约束链。
关键心智模型
模型 1:高度继承链
h-full 表示 height: 100%。100% 是谁的 100%?是父元素计算高度的 100%。
失效(链条不完整):
<html> <!-- 无高度 -->
<body> <!-- 无高度 -->
<div class="h-full"> <!-- 没有可继承的高度 = 0 -->
可用(链条完整):
<html class="h-full"> <!-- 视口的 100% -->
<body class="h-full"> <!-- html 的 100% -->
<div class="h-full"> <!-- body 的 100% = 生效 -->
规则:从元素向上追溯到 <html>。每个祖先都需要显式高度,或使用视口单位(h-screen)打破链条。
模型 2:Flex 溢出陷阱
Flex 子元素有隐式的 min-height: auto,阻止其收缩到内容尺寸以下。
// 失效(无法滚动)
<div className="flex flex-col h-screen">
<main className="flex-1 overflow-y-auto"> {/* 无法收缩! */}
// 可用(正确滚动)
<div className="flex flex-col h-screen">
<main className="flex-1 overflow-y-auto min-h-0"> {/* 可以收缩 */}
规则:需要滚动的 Flex 子元素需要 min-h-0。不应收缩的子元素需要 shrink-0。
模型 3:Grid 父子分离
Grid 定义在父元素上。子元素只是占据单元格。
// 失效
<div className="grid-cols-3"> {/* 缺少 'grid'! */}
// 可用
<div className="grid grid-cols-3"> {/* 'grid' 启用 grid-cols-* */}
规则:必须先声明 flex 或 grid,方向/模板类才能生效。
模型 4:滚动容器尺寸
滚动容器需要显式尺寸才能判断何时滚动。
// 失效(永不滚动)
<ScrollArea> {/* 无高度约束 */}
// 可用(由 flex 约束)
<div className="flex flex-col h-screen">
<ScrollArea className="flex-1 min-h-0">
诊断状态
- SL1:高度链断裂。症状:元素塌陷,h-full 不生效。修复:追溯到 html,添加高度或使用 h-screen。
- SL2:Flex 溢出受阻。症状:滚动不工作,内容溢出。修复:为需要滚动的 flex 子元素添加 min-h-0。
- SL3:Grid 结构错误。症状:项目纵向堆叠而非分列。修复:确保父元素上有 grid grid-cols-*。
- SL4:样式未应用。症状:组件无样式,颜色错误。修复:检查 Tailwind content 路径、globals.css 中的 CSS 变量。
- SL5:组件依赖缺失。症状:“Module not found”,功能损坏。修复:npx shadcn add [component],安装 peer 依赖。
常见布局模式
整页应用外壳:
// 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>
带页头的仪表盘:
<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>
卡片网格:
<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>
反模式
- 高度假设:使用 h-full 而未验证祖先链。修复:追溯到 html。使用 h-screen 打破链条。
- 忽视溢出:在 flex 子元素上添加 overflow-y-auto 却没有 min-h-0。修复:flex 子元素需要 min-h-0 才能收缩。
- 猜测导入:猜测 shadcn/ui 之类的导入路径。修复:检查 components.json 中的别名。通常是 @/components/ui/*。
- 扁平复合组件:将复合组件扁平化(如 Dialog 缺少 DialogTrigger/DialogContent)。修复:保持所需的嵌套结构。
生成前检查清单
- 导入别名已知(@/components/ui/*)
- html 有 h-full
- body 有 h-full 或 min-h-full
- 滚动容器有显式高度
- Flex 滚动子元素有 min-h-0
- 固定元素有 shrink-0
相关技能
- frontend-design:实现前的设计决策
- react-pwa:React 应用的 PWA 功能