Overlay
Modal
A dialog modal with header, body, and footer sections. Supports scrollable content, fullscreen mode, custom headers, and non-dismissible state.
Basic Usage
Use bind:open to control visibility.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Modal" onclick={() => (open = true)} />
<Modal
bind:open
title="Basic Modal"
description="This is a simple modal with a title and description."
>
{#snippet body()}
<p>This is the body content of the modal.</p>
{/snippet}
</Modal>With Footer
Add action buttons in the footer.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Modal" onclick={() => (open = true)} />
<Modal
bind:open
title="Confirm Action"
description="Are you sure you want to proceed?"
>
{#snippet body()}
<p>This action cannot be undone. Please confirm to continue.</p>
{/snippet}
{#snippet footer()}
<Button variant="outline" color="surface" label="Cancel" onclick={() => (open = false)} />
<Button label="Confirm" onclick={() => (open = false)} />
{/snippet}
</Modal>Scrollable
Enable scrolling for long content.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Scrollable" onclick={() => (open = true)} />
<Modal
bind:open
title="Terms of Service"
scrollable
>
{#snippet body()}
<div class="space-y-4">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
<!-- Long scrollable content -->
</div>
{/snippet}
</Modal>Fullscreen
Fill the entire viewport.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Fullscreen" onclick={() => (open = true)} />
<Modal
bind:open
title="Fullscreen Modal"
fullscreen
>
{#snippet body()}
<p>This modal fills the entire viewport.</p>
{/snippet}
</Modal>Custom Header
Replace the default header with a custom layout.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Custom Header" onclick={() => (open = true)} />
<Modal bind:open>
{#snippet header()}
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10">
<span class="text-primary text-lg">!</span>
</div>
<div>
<h3 class="text-lg font-semibold">Custom Header</h3>
<p class="text-sm text-on-surface/60">With a custom icon layout</p>
</div>
</div>
{/snippet}
{#snippet body()}
<p>The header snippet lets you fully customize the header area.</p>
{/snippet}
</Modal>Nested Modals
Stack modals on top of each other.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let outerOpen = $state(false);
let innerOpen = $state(false);
</script>
<Button label="Open Outer Modal" onclick={() => (outerOpen = true)} />
<Modal bind:open={outerOpen} title="Outer Modal">
{#snippet body()}
<p>This is the outer modal. Click below to open a nested modal.</p>
<Button label="Open Inner Modal" variant="outline" onclick={() => (innerOpen = true)} class="mt-4" />
{/snippet}
{#snippet footer()}
<Button variant="outline" label="Close" onclick={() => (outerOpen = false)} />
{/snippet}
</Modal>
<Modal bind:open={innerOpen} title="Inner Modal" description="This is a nested modal on top of the outer one.">
{#snippet body()}
<p>You can stack modals on top of each other.</p>
{/snippet}
{#snippet footer()}
<Button label="Close Inner" onclick={() => (innerOpen = false)} />
{/snippet}
</Modal>With Form & SelectMenu
A practical example combining Modal with form controls including a searchable SelectMenu.
<script lang="ts">
import { Modal, Button, Input, Textarea, Select, SelectMenu, FormField } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Create Task" onclick={() => (open = true)} />
<Modal bind:open title="Create New Task" description="Fill in the details below.">
{#snippet body()}
<form class="space-y-4">
<FormField label="Title" required>
<Input placeholder="Task title..." />
</FormField>
<FormField label="Assignee">
<SelectMenu
placeholder="Search team members..."
items={[
{ value: 'alice', label: 'Alice Martin', avatar: { src: 'https://i.pravatar.cc/40?u=alice', alt: 'Alice' } },
{ value: 'bob', label: 'Bob Wilson', avatar: { src: 'https://i.pravatar.cc/40?u=bob', alt: 'Bob' } },
{ value: 'carol', label: 'Carol Lee', avatar: { src: 'https://i.pravatar.cc/40?u=carol', alt: 'Carol' } },
{ value: 'dave', label: 'Dave Kim', avatar: { src: 'https://i.pravatar.cc/40?u=dave', alt: 'Dave' } }
]}
/>
</FormField>
<FormField label="Priority">
<Select
placeholder="Select priority"
items={[
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
{ value: 'urgent', label: 'Urgent' }
]}
/>
</FormField>
<FormField label="Description" hint="Optional">
<Textarea placeholder="Describe the task..." autoresize />
</FormField>
</form>
{/snippet}
{#snippet footer()}
<Button variant="outline" color="surface" label="Cancel" onclick={() => (open = false)} />
<Button label="Create Task" onclick={() => (open = false)} />
{/snippet}
</Modal>Non-Dismissible
Prevent closing via overlay click or Escape.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Non-dismissible" onclick={() => (open = true)} />
<Modal
bind:open
title="Important Notice"
dismissible={false}
>
{#snippet body()}
<p>This modal cannot be closed by clicking outside or pressing Escape. You must use the button below.</p>
{/snippet}
{#snippet footer()}
<Button label="I Understand" onclick={() => (open = false)} />
{/snippet}
</Modal>UI Slots
Use the ui prop to override classes on internal elements.
| Slot | Description |
|---|---|
overlay | Backdrop overlay |
content | Modal content container |
header | Header section |
wrapper | Title/description wrapper |
title | Title text |
description | Description text |
actions | Actions area (between title and close) |
body | Main content area |
footer | Footer section |
close | Close button area |
Snippets
| Snippet | Description |
|---|---|
children | Trigger element (clicking opens modal) |
content | Replace entire default layout |
header | Custom header (replaces default) |
titleSlot | Custom title (overrides title prop) |
descriptionSlot | Custom description |
actions | Actions between title and close button |
body | Body content |
footer | Footer content |
closeSlot | Custom close button |
Props
| Prop | Type | Default |
|---|---|---|
open | boolean | false |
title | string | - |
description | string | - |
overlay | boolean | true |
scrollable | boolean | false |
fullscreen | boolean | false |
close | boolean | CloseProps | true |
dismissible | boolean | true |
transition | boolean | true |
portal | boolean | true |
onOpenChange | (open) => void | - |
ref | HTMLElement | null | null |
class | string | - |
ui | Record<Slot, Class> | - |