Overlay

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").

Migration
<!-- 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.

Side:
Size:
<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

SlotDescription
overlayBackdrop overlay
contentMain content container
headerHeader section
wrapperTitle/description wrapper
titleTitle text
descriptionDescription text
actionsActions area in header
bodyBody content
footerFooter section
closeClose button area

Snippets

SnippetDescription
childrenTrigger element. Receives a { props } argument you must spread onto your own focusable element.
contentReplace entire layout
headerCustom header
titleSlotCustom title
descriptionSlotCustom description
actionsHeader actions
bodyBody content
footerFooter content
closeSlotCustom close button

Props

PropTypeDefault
openbooleanfalse
side'top'|'right'|'bottom'|'left''right'
size'sm' | 'md' | 'lg' | 'xl' | 'full''md'
titlestring-
descriptionstring-
insetbooleanfalse
overlaybooleantrue
closeboolean | ClosePropstrue
dismissiblebooleantrue
transition'none' | 'fade' | 'slide' | 'scale' | boolean'slide'
portalbooleantrue
classstring-
uiRecord<Slot, Class>-