# Component Composition ## Contents - Items always inside their Group component - Callouts use Alert - Empty states use Empty component - Toast notifications use svelte-sonner - Choosing between overlay components - Dialog, Sheet, and Drawer always need a Title - Card structure - Button has no isPending or isLoading prop - Tabs.Trigger must be inside Tabs.List - Avatar always needs Avatar.Fallback - 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:** ```svelte Apple Banana ``` **Correct:** ```svelte Apple Banana ``` This applies to all group-based components: | Item | Group | | ------------------------------------------------------------- | -------------------- | | `Select.Item`, `Select.Label` | `Select.Group` | | `DropdownMenu.Item`, `DropdownMenu.Label`, `DropdownMenu.Sub` | `DropdownMenu.Group` | | `Menubar.Item` | `Menubar.Group` | | `ContextMenu.Item` | `ContextMenu.Group` | | `Command.Item` | `Command.Group` | --- ## Callouts use Alert ```svelte Warning Something needs attention. ``` --- ## Empty states use Empty component ```svelte No projects yet Get started by creating a new project. ``` --- ## Toast notifications use svelte-sonner ```svelte ``` ```ts toast.success("Changes saved."); toast.error("Something went wrong."); toast("File deleted.", { action: { label: "Undo", onClick: () => undoDelete() }, }); ``` Mount the `Toaster` from your UI folder once in the app layout (see [Sonner](https://shadcn-svelte.com/docs/components/sonner)). --- ## 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 `Dialog.Title`, `Sheet.Title`, `Drawer.Title` are required for accessibility. Use `class="sr-only"` if visually hidden. ```svelte Edit Profile Update your profile. ... ``` --- ## Card structure Use full composition — don't dump everything into `Card.Content`: ```svelte Team Members Manage your team. ... ``` --- ## Button has no isPending or isLoading prop Compose with `Spinner` inside `Button` + `disabled`: ```svelte ``` --- ## Tabs.Trigger must be inside Tabs.List Never render `Tabs.Trigger` directly inside `Tabs.Root` — always wrap in `Tabs.List`: ```svelte Account Password ... ``` --- ## Avatar always needs Avatar.Fallback Always include `Avatar.Fallback` for when the image fails to load: ```svelte JD ``` --- ## Use existing components instead of custom markup | Instead of | Use | | ---------------------------------------------- | ------------------------------------------------------------------------------------------- | | `
` or `
` | `` (`import { Separator } from "$lib/components/ui/separator"`) | | `
` with styled divs | `` (`import { Skeleton } from "$lib/components/ui/skeleton"`) | | `` | `` (`import { Badge } from "$lib/components/ui/badge"`) |