Overlay
Drawer
A slide-out panel with 4 directions, inset mode, drag handle, header/body/footer sections, and snap points. Built on vaul-svelte.
Basic Usage
Use bind:open to control visibility.
<script lang="ts">
import { Drawer, Button } from 'sv5ui';
let open = $state(false);
</script>
<Drawer bind:open title="Basic Drawer" description="This is a simple drawer example.">
<Button label="Open Drawer" onclick={() => (open = true)} />
{#snippet body()}
<p>This is the drawer body content.</p>
{/snippet}
</Drawer>Directions
Slide from any edge: bottom, left, right, top.
<Drawer bind:open direction="bottom" title="Bottom Drawer">
<Button label="Bottom" onclick={() => (open = true)} />
{#snippet body()}
<p>Slides in from the bottom.</p>
{/snippet}
</Drawer>
<Drawer bind:open direction="left" title="Left Drawer">
<Button label="Left" onclick={() => (open = true)} />
{#snippet body()}
<p>Slides in from the left.</p>
{/snippet}
</Drawer>
<Drawer bind:open direction="right" title="Right Drawer">
<Button label="Right" onclick={() => (open = true)} />
{#snippet body()}
<p>Slides in from the right.</p>
{/snippet}
</Drawer>
<Drawer bind:open direction="top" title="Top Drawer">
<Button label="Top" onclick={() => (open = true)} />
{#snippet body()}
<p>Slides in from the top.</p>
{/snippet}
</Drawer>Inset
Rounded corners with padding from edges.
<Drawer bind:open inset title="Inset Drawer" description="Rounded corners with padding from edges.">
<Button label="Open Inset Drawer" onclick={() => (open = true)} />
{#snippet body()}
<p>This drawer has inset styling with rounded corners.</p>
{/snippet}
</Drawer>With Footer
Add actions in the footer.
<Drawer bind:open title="Confirm Action" description="Are you sure you want to proceed?">
<Button label="Open Drawer" onclick={() => (open = true)} />
{#snippet body()}
<p>This action cannot be undone. Please review before confirming.</p>
{/snippet}
{#snippet footer()}
<div class="flex gap-2 justify-end">
<Button variant="outline" label="Cancel" onclick={() => (open = false)} />
<Button label="Confirm" onclick={() => (open = false)} />
</div>
{/snippet}
</Drawer>No Handle
Hide the drag handle.
<Drawer bind:open handle={false} title="No Handle" description="This drawer has no drag handle.">
<Button label="Open Drawer" onclick={() => (open = true)} />
{#snippet body()}
<p>The drag handle is hidden.</p>
{/snippet}
</Drawer>Nested Drawers
Stack drawers using the nested prop on the inner drawer.
<script lang="ts">
import { Drawer, Button } from 'sv5ui';
let outerOpen = $state(false);
let innerOpen = $state(false);
</script>
<Drawer bind:open={outerOpen} title="Settings" description="Manage your preferences.">
<Button label="Open Settings" onclick={() => (outerOpen = true)} />
{#snippet body()}
<div class="space-y-4">
<p>Main settings panel.</p>
<!-- Nested drawer must be inside the outer drawer -->
<Drawer bind:open={innerOpen} nested title="Advanced Settings">
<Button label="Advanced Settings" variant="outline" onclick={() => (innerOpen = true)} />
{#snippet body()}
<p>This is a nested drawer on top of the outer one.</p>
{/snippet}
{#snippet footer()}
<Button label="Back" onclick={() => (innerOpen = false)} class="w-full" />
{/snippet}
</Drawer>
</div>
{/snippet}
</Drawer>With Form & SelectMenu
A right-side drawer with a full contact form including searchable SelectMenu.
<script lang="ts">
import { Drawer, Button, Input, Textarea, Select, SelectMenu, FormField } from 'sv5ui';
let open = $state(false);
</script>
<Drawer bind:open direction="right" title="New Contact" description="Add a new contact to your list.">
<Button label="Add Contact" onclick={() => (open = true)} />
{#snippet body()}
<form class="space-y-4">
<FormField label="Name" required>
<Input placeholder="Full name" />
</FormField>
<FormField label="Email" required>
<Input type="email" placeholder="email@example.com" leadingIcon="lucide:mail" />
</FormField>
<FormField label="Company">
<SelectMenu
placeholder="Search companies..."
items={[
{ value: 'google', label: 'Google', icon: 'logos:google-icon' },
{ value: 'apple', label: 'Apple', icon: 'logos:apple' },
{ value: 'microsoft', label: 'Microsoft', icon: 'logos:microsoft-icon' },
{ value: 'meta', label: 'Meta', icon: 'logos:meta-icon' }
]}
/>
</FormField>
<FormField label="Role">
<Select placeholder="Select role" items={[
{ value: 'ceo', label: 'CEO' },
{ value: 'cto', label: 'CTO' },
{ value: 'designer', label: 'Designer' },
{ value: 'engineer', label: 'Engineer' }
]} />
</FormField>
<FormField label="Notes" hint="Optional">
<Textarea placeholder="Additional notes..." autoresize />
</FormField>
</form>
{/snippet}
{#snippet footer()}
<div class="flex w-full justify-end gap-2">
<Button variant="outline" label="Cancel" onclick={() => (open = false)} />
<Button label="Save Contact" onclick={() => (open = false)} />
</div>
{/snippet}
</Drawer>Non-Dismissible
Prevent closing via overlay click or dragging.
<Drawer bind:open dismissible={false} title="Non-Dismissible" description="You must use the button to close.">
<Button label="Open Drawer" onclick={() => (open = true)} />
{#snippet body()}
<p>This drawer cannot be dismissed by clicking the overlay or dragging.</p>
{/snippet}
{#snippet footer()}
<Button label="Close" onclick={() => (open = false)} class="w-full" />
{/snippet}
</Drawer>UI Slots
Use the ui prop to override classes on internal elements.
| Slot | Description |
|---|---|
overlay | Backdrop overlay |
content | Main drawer content |
handle | Drag handle |
container | Container wrapper |
header | Header section |
title | Title text |
description | Description text |
body | Main content area |
footer | Footer section |
Snippets
| Snippet | Description |
|---|---|
children | Trigger element |
content | Replace entire default layout |
header | Custom header |
titleSlot | Custom title (overrides title prop) |
descriptionSlot | Custom description |
body | Body content |
footer | Footer content |
Props
| Prop | Type | Default |
|---|---|---|
open | boolean | false |
direction | 'top' | 'right' | 'bottom' | 'left' | 'bottom' |
title | string | - |
description | string | - |
inset | boolean | false |
overlay | boolean | true |
handle | boolean | true |
dismissible | boolean | true |
portal | boolean | true |
snapPoints | (number | string)[] | - |
nested | boolean | false |
onOpenChange | (open) => void | - |
class | string | - |
ui | Record<Slot, Class> | - |