# 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
AppleBanana
```
**Correct:**
```svelte
AppleBanana
```
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
WarningSomething needs attention.
```
---
## Empty states use Empty component
```svelte
No projects yetGet 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 ProfileUpdate your profile.
...
```
---
## Card structure
Use full composition — don't dump everything into `Card.Content`:
```svelte
Team MembersManage 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
AccountPassword...
```
---
## 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 |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `