# Component Composition ## Contents - Items always inside their Group component - Callouts use Alert - Empty states use Empty component - Toast notifications use vue-sonner - Choosing between overlay components - Dialog, Sheet, and Drawer always need a Title - Card structure - Button has no isPending or isLoading prop - TabsTrigger must be inside TabsList - Avatar always needs AvatarFallback - Use Separator instead of raw hr or border divs - Use Skeleton for loading placeholders - Use Badge instead of custom styled spans --- ## Items always inside their Group component Never render items directly inside the content container. **Incorrect:** ```html Apple Banana ``` **Correct:** ```html Apple Banana ``` This applies to all group-based components: | Item | Group | |------|-------| | `SelectItem`, `SelectLabel` | `SelectGroup` | | `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` | | `MenubarItem` | `MenubarGroup` | | `ContextMenuItem` | `ContextMenuGroup` | | `CommandItem` | `CommandGroup` | --- ## Callouts use Alert ```html Warning Something needs attention. ``` --- ## Empty states use Empty component ```html No projects yet Get started by creating a new project. ``` --- ## Toast notifications use vue-sonner ```js ``` --- ## Choosing between overlay components | Use case | Component | |----------|-----------| | Focused task that requires input | `Dialog` | | Destructive action confirmation | `AlertDialog` | | Side panel with details or filters | `Sheet` | | Mobile-first bottom panel | `Drawer` | | Quick info on hover | `HoverCard` | | Small contextual content on click | `Popover` | --- ## Dialog, Sheet, and Drawer always need a Title `DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `class="sr-only"` if visually hidden. ```vue Edit Profile Update your profile. ... ``` --- ## Card structure Use full composition — don't dump everything into `CardContent`: ```html Team Members Manage your team. ... ``` --- ## Button has no isPending or isLoading prop Compose with `Spinner` + `data-icon` + `disabled`: ```html ``` --- ## TabsTrigger must be inside TabsList Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`: ```html Account Password ... ``` --- ## Avatar always needs AvatarFallback Always include `AvatarFallback` for when the image fails to load: ```html JD ``` --- ## Use existing components instead of custom markup | Instead of | Use | |---|---| | `
` or `
` | `` | | `
` with styled divs | `` | | `` | `` |