shadcn/ui 组件组合规则详解:从 Group 嵌套、浮层选型到禁用自定义标记的完整实践

shadcn/ui 组件组合规则详解:从 Group 嵌套、浮层选型到禁用自定义标记的完整实践 shadcn/ui 组件组合规则详解从 Group 嵌套、浮层选型到禁用自定义标记的完整实践【免费下载链接】uiA set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code.项目地址: https://gitcode.com/GitHub_Trending/ui/ui本篇技术指南围绕 shadcn/ui 仓库中 composition.md 这份组件组合Composition规则文档展开系统讲解 13 条“始终强制执行”的组合约定Item 必须嵌套在 Group 中、浮层组件如何选型、Toast 如何跟随项目 base、Button 加载态为何不用isPending等。读完后你将掌握在 shadcn/ui 项目中写出结构正确、可访问、可复用代码的组合范式并能直接对照仓库中的注册表源码验证每条规则的实现依据。规则文档在项目中的位置composition.md 是 shadcn Agent 技能SKILL.md“Critical Rules”关键规则体系的组成部分。在 SKILL.md 中这份文档被两次引用Component Structure组件结构覆盖 Group 嵌套、Dialog/Sheet/Drawer 的 Title、Card 完整组合、Button 加载态、TabsTrigger 嵌套、AvatarFallbackUse Components, Not Custom Markup用组件而非自定义标记覆盖 Alert、Empty、Toast、Separator、Skeleton、Badge。规则文档自身以“Incorrect / Correct”代码对的格式给出反例与正例属于可被 Agent 与人类开发者共同遵循的强制约定而非建议性风格。仓库中的注册表组件源码registry/bases/*、registry/new-york-v4/*则提供了每条规则落地的实现证据。Items 必须嵌套在 Group 组件内第一条规则是永远不要将 item 直接渲染在内容容器内必须包一层对应的 Group。错误写法SelectContent SelectItem valueappleApple/SelectItem SelectItem valuebananaBanana/SelectItem /SelectContent正确写法SelectContent SelectGroup SelectItem valueappleApple/SelectItem SelectItem valuebananaBanana/SelectItem /SelectGroup /SelectContent规则文档给出了完整的 Item → Group 映射表适用于所有基于分组的组件ItemGroupSelectItem、SelectLabelSelectGroupDropdownMenuItem、DropdownMenuLabel、DropdownMenuSubDropdownMenuGroupMenubarItemMenubarGroupContextMenuItemContextMenuGroupCommandItemCommandGroupMessageScrollerItemMessageScrollerContentMessage连续、同一发送者MessageGroupBubble堆叠BubbleGroupAttachment同一行AttachmentGroup对于聊天组件嵌套顺序是固定的MessageScrollerProvider→MessageScroller→MessageScrollerViewport→MessageScrollerContent→MessageScrollerItem更细的规则见 chat.md。仓库中的实现佐证了这一结构聊天滚动组件的完整源码位于 message-scroller.tsx其中MessageScrollerItem与MessageScrollerContent作为独立导出的组合子部件存在说明“Item 必须在 Content/Group 内”不是文档约定而是组件本身的结构设计。提示框Callout统一使用 Alert页面中需要引起用户注意的提示信息一律使用Alert不要手写带边框的divAlert AlertTitleWarning/AlertTitle AlertDescriptionSomething needs attention./AlertDescription /Alert从注册表源码可以确认其组合结构与能力。以 alert.tsx 为例Alert根元素渲染为div携带rolealert与data-slotalert并支持default与destructive两个variant通过 CVAclass-variance-authority管理样式AlertTitle与AlertDescription通过col-start-2等 CSS Grid 布局类实现标题/描述与图标的两列排布根类中的grid-cols-[0_1fr]与has-[svg]:grid-cols-[calc(var(--spacing)*4)_1fr]表明传入子级 SVG 时自动出现图标列destructive变体会将[svg]与描述文本一并染为 destructive 色。这意味着使用Alert时语义角色、变体切换、图标排布全部由组件承担无需任何额外标记。空状态Empty state使用 Empty 组件空列表、无数据等场景不要拼凑自定义标记统一使用Empty组合结构Empty EmptyHeader EmptyMedia varianticonFolderIcon //EmptyMedia EmptyTitleNo projects yet/EmptyTitle EmptyDescriptionGet started by creating a new project./EmptyDescription /EmptyHeader EmptyContent ButtonCreate Project/Button /EmptyContent /Empty结构上分为两部分EmptyHeader承载媒体EmptyMedia示例中使用varianticon、标题与描述EmptyContent承载行动按钮等后续操作。该组件在注册表中以 empty.tsx 提供Base UI 版在 new-york-v4/ui/empty.tsx 中亦有对应实现说明它是跨 base 的标准组件。Toast 通知跟随项目的 base 选择Toast 的实现取决于项目的base字段可通过npx shadcnlatest info查看Base UI 项目使用toast组件以命令式 API 添加通知import { toast } from /components/ui/toast toast.add({ title: Changes saved., })Radix 与 React Aria 项目则使用 Sonnerimport { toast } from sonner toast.success(Changes saved.) toast.error(Something went wrong.) toast(File deleted., { action: { label: Undo, onClick: () undoDelete() }, })仓库源码印证了两条路线的共存Base UI 路线的完整实现见 toast.tsx文件顶部通过ToastPrimitive.createToastManager()创建toast单例并导出toast、useToastManager、createToastManager这正是文档中toast.add(...)命令式调用的来源Toaster组件内部组合了ToastProvider→ToastPortal→ToastViewport→ToastList其中ToastList用useToastManager()读取toasts并逐条渲染ToastToastContentToastIcon/ToastTitle/ToastDescription/ToastAction/ToastClose。ToastIcon还支持success、info、warning、error、loading五种类型的图标toast.tsx#L142-L224示例可参考 toast-example.tsxRadix/Aria 路线见 sonner.tsx用法示例见 sonner-example.tsx。因此在写 Toast 前先确认项目 base混用如在 Base UI 项目里引入 Sonner会破坏与主题变量的联动。浮层组件选型Dialog、Sheet、Drawer 与轻信息浮层规则文档给出了六类浮层场景的选型表使用场景组件需要输入的重点任务Dialog破坏性操作的确认AlertDialog带详情或筛选器的侧边面板Sheet移动优先的底部面板Drawer悬停时的快速信息HoverCard点击后的小块上下文内容Popover这些组件在注册表中均有对应实现例如 dialog.tsx、sheet.tsx、drawer.tsx、alert-dialog.tsx、hover-card.tsx选型时按“交互语义”而不是“视觉相似度”判断。Dialog、Sheet、Drawer 必须带 TitleDialogTitle、SheetTitle、DrawerTitle是可访问性accessibility必需项必须始终存在。如果需要视觉上隐藏标题使用classNamesr-only而不是直接省略DialogContent DialogHeader DialogTitleEdit Profile/DialogTitle DialogDescriptionUpdate your profile./DialogDescription /DialogHeader ... /DialogContent这条规则的原因在于浮层组件的无障碍实现依赖标题节点作为aria-labelledby的指向目标省略标题会导致屏幕阅读器无法获知浮层用途。sr-only方案在保留 DOM 结构的同时实现了视觉隐藏两者兼得。Card 使用完整组合不要把内容全塞进 CardContent规则要求使用完整的 Card 组合结构而不是把所有东西堆进CardContentCard CardHeader CardTitleTeam Members/CardTitle CardDescriptionManage your team./CardDescription /CardHeader CardContent.../CardContent CardFooter ButtonInvite/Button /CardFooter /Card五个子部件各司其职CardHeader放标题与描述、CardContent放主体、CardFooter放尾部操作区。card.tsx 的注册表实现中每个子部件都带有独立的data-slot如data-slotcard这也意味着下游可以通过 slot 选择器精细定制各区域样式——拆得越完整可定制面越大。Button 没有 isPending / isLoading 属性shadcn/ui 的Button不提供isPending或isLoading这类加载态属性。正确做法是用Spinnerdata-icondisabled组合表达Button disabled Spinner>Tabs defaultValueaccount TabsList TabsTrigger valueaccountAccount/TabsTrigger TabsTrigger valuepasswordPassword/TabsTrigger /TabsList TabsContent valueaccount.../TabsContent /TabsTabsList承担触发器组的样式容器职责背景、圆角、聚焦环tabs.tsx 中对TabsList与TabsTrigger的样式分工印证了这一点跳过TabsList会让触发器失去组级样式并破坏键盘导航的视觉反馈。Avatar 必须始终搭配 AvatarFallback头像图片加载失败时需要兜底因此AvatarFallback是必需的Avatar AvatarImage src/avatar.png altUser / AvatarFallbackJD/AvatarFallback /Avatar规则的理由很直接没有AvatarFallback时图片 404 或网络异常会导致头像位置出现空白而AvatarFallback通常放姓名首字母保证了视觉与语义的双重兜底。用现成组件替代自定义标记规则的最后一条是“先查组件库再写自定义标记”的三组替代映射不要用改用hr或div classNameborder-tSeparator /带样式的div classNameanimate-pulseSkeleton classNameh-4 w-3/4 /span classNamerounded-full bg-green-100 ...Badge variantsecondary三者均在注册表中有对应实现separator.tsx、skeleton.tsx、badge.tsx。使用现成组件的收益在于它们内置了主题变量如text-muted-foreground、bg-primary、data-slot钩子与变体系统且会随注册表更新而演进手写的div classNameborder-t则既无法跟随主题也容易被后续维护者误当成一次性标记而改坏。小结组合规则的核查清单将 composition.md 的 13 条规则收敛为一份可逐项自查的清单SelectItem/DropdownMenuItem/CommandItem等是否都包在对应 Group 内对照上文映射表提示条是否用了Alertdefault/destructive变体而非自定义 div空状态是否用了EmptyEmptyHeader/EmptyContentToast 是否匹配项目 baseBase UI →toast组件Radix/Aria → Sonner浮层选型是否按场景对应 Dialog/AlertDialog/Sheet/Drawer/HoverCard/PopoverDialog/Sheet/Drawer 是否都有*Title视觉隐藏时用sr-onlyCard 是否使用了 Header/Title/Description/Content/Footer 完整结构按钮加载态是否用Spinnerdata-icondisabled组合而非不存在的isPendingTabsTrigger是否包在TabsList内Avatar是否带了AvatarFallback分隔线/骨架屏/徽章是否分别用了Separator/Skeleton/Badge。这 13 条规则的共同点是把结构与语义的职责交给组件的官方组合方式而不是用通用 HTML 标记“模拟”一个组件的外观。SKILL.md 将 composition.md 与 styling.md、forms.md、chat.md 等并列为“始终强制”的 Critical Rules配合 base-vs-radix.mdasChild与render的差异构成了 shadcn/ui 项目内组件代码的正确性基线。【免费下载链接】uiA set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code.项目地址: https://gitcode.com/GitHub_Trending/ui/ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考