Skip to content

Slideover

A panel that slides in from an edge of the screen, built on Reka UI's Dialog primitive.

Usage

Slideover visibility is controlled with v-model:open, so it needs a ref from the page it's used on.

vue
<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <SButton @click="open = true">
    Open slideover
  </SButton>

  <SSlideover v-model:open="open" title="Edit profile" description="Update your personal details.">
    <template #body>
      Slideover content goes here.
    </template>
    <template #footer>
      <SButton variant="ghost" @click="open = false">
        Cancel
      </SButton>
      <SButton @click="open = false">
        Save
      </SButton>
    </template>
  </SSlideover>
</template>

Always pass title (or a header/content slot containing a heading) - without one, the panel has no accessible name for screen readers, and dev mode warns about it.

Side

side picks which edge the panel slides in from - top, right (default), bottom, or left:

vue-html
<SSlideover v-model:open="open" side="left" title="Filters" />

Inset

inset floats the panel with a margin and rounded corners instead of sitting flush against the edge:

vue-html
<SSlideover v-model:open="open" inset title="Notifications" />

Custom content

content replaces the header/title/description/body/footer structure entirely - useful when the panel doesn't fit the header-plus-body shape at all. close is ignored once content is provided, since the slot owns the whole visible panel - but title/description, if still set, stay registered with Reka as the panel's accessible name/description, just visually hidden instead of rendered as a header. Pass them if your own custom content doesn't already include a heading Reka can associate with the panel:

vue-html
<SSlideover v-model:open="open">
  <template #content>
    <div class="flex flex-col items-center gap-4 p-8 text-center">
      ...
    </div>
  </template>
</SSlideover>

Non-modal

modal="false" stops the panel from blocking interaction with the rest of the page - no focus trap, outside elements stay reachable and aren't hidden from assistive tech. overlay is independent of this - a non-modal panel can still show (or hide) its own backdrop:

vue-html
<SSlideover v-model:open="open" :modal="false" title="Non-modal" />

Scrollable content

The panel is capped to the viewport - a body taller than that scrolls internally on its own, while the header and footer stay pinned in place.

Intercepting dismissal

SSlideover doesn't override Reka UI's own defaults (Escape and an outside click both dismiss it), but exposes the underlying events so you can preventDefault() on them - e.g. to confirm before closing a panel with unsaved changes:

vue-html
<SSlideover
  v-model:open="open"
  title="Edit profile"
  @escape-key-down="(e) => hasChanges && e.preventDefault()"
  @pointer-down-outside="(e) => hasChanges && e.preventDefault()"
/>

For the simpler, all-or-nothing case - a panel that must not be dismissed by anything but an explicit choice - dismissible="false" disables both Escape and outside-click at once, and close="false" removes the close button too:

vue-html
<SSlideover v-model:open="open" :dismissible="false" :close="false" title="Confirm your plan" />

The two raw events still fire even with dismissible="false" - useful for something like a shake animation to signal the panel won't close that way.

Customizing the close button

The close button renders as a real <SButton>, so :ui.close already reaches it fully - not just classes. An object :ui value's non-class keys apply as raw attrs/props, so :ui="{ close: { color: 'danger' } }" works today without a dedicated prop for it:

vue-html
<SSlideover v-model:open="open" title="Delete item" :ui="{ close: { color: 'danger' } }" />

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's the panel's own theme file:

Theme source "slideover" not found

Programmatic usage

useSlideover() opens a panel from anywhere - a click handler, an async callback - without a v-model of your own to manage. It works automatically once <SApp> wraps your app, exactly like useModal(); there's nothing extra to place.

The given component owns the panel's entire content, the same as the content slot above - its own heading, its own buttons. open() returns a promise that resolves with whatever value the component's own close event carries, so an explicit action can resolve with real data while dismissing via Escape or an outside click resolves with undefined:

vue-html
<!-- SlideoverFilterPanel.vue - a self-contained panel -->
<template>
  <div class="flex h-full flex-col">
    <h2>Filters</h2>
    <SCheckbox label="In stock only" />
    <SButton variant="ghost" @click="$emit('close', false)">Cancel</SButton>
    <SButton @click="$emit('close', true)">Apply</SButton>
  </div>
</template>
ts
import SlideoverFilterPanel from './SlideoverFilterPanel.vue'

const slideover = useSlideover()
const applied = await slideover.open(SlideoverFilterPanel)
// true, false, or undefined if dismissed without an explicit choice

open() also takes the same title/description/side/inset/ dismissible/modal/overlay/transition options as the declarative props:

ts
await slideover.open(SlideoverFilterPanel, { side: 'left' })

Props

PropTypeDefault
openbooleanfalse
titlestring-
descriptionstring-
side'top' | 'right' | 'bottom' | 'left''right'
insetbooleanfalse
dismissiblebooleantrue
closebooleantrue
modalbooleantrue
overlaybooleantrue
transitionbooleantrue
uiPartial<Record<'overlay' | 'content' | 'header' | 'title' | 'description' | 'close' | 'body' | 'footer', string | object>>-

A class passed to <SSlideover> is merged into the slideover panel. Use ui.content for the same panel, or put a class directly on the default-slot trigger to style that trigger.

Events

EventPayloadDescription
update:openbooleanOpen state changed
escapeKeyDownKeyboardEventEscape was pressed - preventDefault() to stop it from closing
pointerDownOutsideEventA pointer went down outside the panel - preventDefault() to stop it from closing
focusOutsideEventA non-modal panel's outside element received focus - preventDefault() to stop it from closing
afterLeave-The close transition has finished (or fires immediately if transition is false)

Slots

SlotDescription
defaultThe trigger element
contentReplaces the visible header/body/footer entirely (title/description, if set, still register for a11y)
headerOverrides the default title/description block
bodyMain content
footerUsually action buttons
close-iconReplaces the close button's icon (default: hugeicons:cancel-01)
Edit this page