Slideover
A slide-out panel similar to Drawer but built on bits-ui Dialog. Supports 4 sides, inset mode, close button, and header/body/footer sections. Also composed by the Header mobile menu (mode="slideover").
<!-- Before (v2.0.0) - trigger auto-wrapped in a <button> -->
<Slideover bind:open title="Basic Slideover">
<Button label="Open Slideover" onclick={() => (open = true)} />
{#snippet body()}...{/snippet}
</Slideover>
<!-- After (v2.1.0) - children is a snippet; spread props onto the trigger -->
<Slideover bind:open title="Basic Slideover">
{#snippet children({ props })}
<Button {...props} label="Open Slideover" />
{/snippet}
{#snippet body()}...{/snippet}
</Slideover>Basic Usage
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Slideover" onclick={() => (open = true)} />
<Slideover
bind:open
title="Basic Slideover"
description="This is a simple slideover panel."
>
{#snippet body()}
<p>This is the body content of the slideover.</p>
{/snippet}
</Slideover>Trigger Snippet
Render the trigger inside the children snippet instead of an external button, and spread the props argument onto your own focusable element.
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
</script>
<Slideover
bind:open
title="Trigger Snippet"
description="The trigger lives inside the children snippet."
>
{#snippet children({ props })}
<Button {...props} label="Open via Trigger" />
{/snippet}
{#snippet body()}
<p>Opened by a trigger that spreads `props` - no external button needed.</p>
{/snippet}
</Slideover>With Footer
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Slideover" onclick={() => (open = true)} />
<Slideover
bind:open
title="Edit Profile"
description="Make changes to your profile here."
>
{#snippet body()}
<p>Form fields go here...</p>
{/snippet}
{#snippet footer()}
<Button variant="outline" color="surface" label="Cancel" onclick={() => (open = false)} />
<Button label="Save Changes" onclick={() => (open = false)} />
{/snippet}
</Slideover>Scrollable
The header and footer stay fixed while the body scrolls inside the panel. Scrolling stays inside the panel - the underlying page never moves. This is the default behavior; no extra prop needed.
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
const sections = Array.from({ length: 20 }, (_, i) => i + 1);
</script>
<Button label="Open Scrollable" onclick={() => (open = true)} />
<!-- No special prop needed - body scrolls inside the panel by default.
Header & footer stay fixed; the underlying page never moves. -->
<Slideover bind:open title="Notification Settings">
{#snippet body()}
<div class="space-y-4">
{#each sections as n (n)}
<p>Item {n}. Long content here...</p>
{/each}
</div>
{/snippet}
{#snippet footer()}
<Button variant="outline" label="Cancel" onclick={() => (open = false)} />
<Button label="Save" onclick={() => (open = false)} />
{/snippet}
</Slideover>Sides
Slide from any edge.
<Slideover side="right" title="Right" bind:open>...</Slideover>
<Slideover side="left" title="Left" bind:open>...</Slideover>
<Slideover side="top" title="Top" bind:open>...</Slideover>
<Slideover side="bottom" title="Bottom" bind:open>...</Slideover>Sizes
Use size to control panel dimension. For left/right it sets max-width; for top/bottom it sets max-height. Pick a side and size from the toolbars below.
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
import type { SlideoverProps } from 'sv5ui';
let open = $state(false);
let side = $state<NonNullable<SlideoverProps['side']>>('right');
let size = $state<NonNullable<SlideoverProps['size']>>('md');
</script>
<!-- size meaning depends on side:
- left / right → max-width
- top / bottom → max-height -->
<Slideover bind:open {side} {size} title="Side: {side} · Size: {size}">
{#snippet body()}
<p>Use side and size together.</p>
{/snippet}
</Slideover>
<!-- Examples -->
<Slideover side="right" size="sm" title="Narrow drawer" />
<Slideover side="right" size="full" title="Full-width drawer" />
<Slideover side="bottom" size="lg" title="Tall bottom sheet" />Transitions
Control entrance/exit animation with transition: 'slide' (default, side-aware), 'fade', 'scale', or 'none'. Legacy boolean still works (true → previous default, false → 'none').
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
let transition = $state<'none' | 'fade' | 'slide' | 'scale'>('slide');
</script>
<Slideover bind:open {transition} title="Transition: {transition}">
{#snippet body()}
<p>Animation: {transition}</p>
{/snippet}
</Slideover>
<!--
'slide' (default) is side-aware: panel slides from the chosen side.
Boolean still supported for back-compat:
- transition={true} → previous default
- transition={false} → 'none'
-->Inset
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Inset" onclick={() => (open = true)} />
<Slideover
bind:open
title="Inset Slideover"
description="Displayed with inset margins and rounded corners."
inset
>
{#snippet body()}
<p>This slideover has inset margins and rounded corners.</p>
{/snippet}
</Slideover>Non-Dismissible
<script lang="ts">
import { Slideover, Button } from 'sv5ui';
let open = $state(false);
</script>
<Button label="Open Non-dismissible" onclick={() => (open = true)} />
<Slideover
bind:open
title="Important"
dismissible={false}
>
{#snippet body()}
<p>This slideover cannot be closed by clicking outside or pressing Escape.</p>
{/snippet}
{#snippet footer()}
<Button label="I Understand" onclick={() => (open = false)} />
{/snippet}
</Slideover>UI Slots
| Slot | Description |
|---|---|
overlay | Backdrop overlay |
content | Main content container |
header | Header section |
wrapper | Title/description wrapper |
title | Title text |
description | Description text |
actions | Actions area in header |
body | Body content |
footer | Footer section |
close | Close button area |
Snippets
| Snippet | Description |
|---|---|
children | Trigger element. Receives a { props } argument you must spread onto your own focusable element. |
content | Replace entire layout |
header | Custom header |
titleSlot | Custom title |
descriptionSlot | Custom description |
actions | Header actions |
body | Body content |
footer | Footer content |
closeSlot | Custom close button |
Props
| Prop | Type | Default |
|---|---|---|
open | boolean | false |
side | 'top'|'right'|'bottom'|'left' | 'right' |
size | 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'md' |
title | string | - |
description | string | - |
inset | boolean | false |
overlay | boolean | true |
close | boolean | CloseProps | true |
dismissible | boolean | true |
transition | 'none' | 'fade' | 'slide' | 'scale' | boolean | 'slide' |
portal | boolean | true |
class | string | - |
ui | Record<Slot, Class> | - |