Slideover

A dialog that slides in from any side of the screen.

Usage

Use a Button or any other component in the default slot of the Slideover.

Then, use the #content slot to add the content displayed when the Slideover is open.

<template>
  <USlideover>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-full m-4" />
    </template>
  </USlideover>
</template>

You can also use the #header, #body and #footer slots to customize the Slideover's content.

Title

Use the title prop to set the title of the Slideover's header.

<template>
  <USlideover title="Slideover with title">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Description

Use the description prop to set the description of the Slideover's header.

<template>
  <USlideover
    title="Slideover with description"
    description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Close

Use the close prop to customize or hide the close button (with false value) displayed in the Slideover's header.

You can pass all the props of the Button component to customize it.

<template>
  <USlideover
    title="Slideover with close button"
    :close="{
      color: 'primary',
      variant: 'outline',
      class: 'rounded-full'
    }"
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>
The close button is not displayed if the #content slot is used as it's a part of the header.

Close Icon

Use the close-icon prop to customize the close button Icon. Defaults to i-lucide-x.

<template>
  <USlideover title="Slideover with close button" close-icon="i-lucide-arrow-right">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>
You can customize this icon globally in your app.config.ts under ui.icons.close key.

Side

Use the side prop to set the side of the screen where the Slideover will slide in from. Defaults to right.

<template>
  <USlideover side="left" title="Slideover with side">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full min-h-48" />
    </template>
  </USlideover>
</template>

Overlay

Use the overlay prop to control whether the Slideover has an overlay or not. Defaults to true.

<template>
  <USlideover :overlay="false" title="Slideover without overlay">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Transition

Use the transition prop to control whether the Slideover is animated or not. Defaults to true.

<template>
  <USlideover :transition="false" title="Slideover without transition">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Prevent close

Use the prevent-close prop to prevent the Slideover from being closed when clicking outside of it.

<template>
  <USlideover prevent-close title="Slideover prevent close">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Examples

Control open state

You can control the open state by using the default-open prop or the v-model:open directive.

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

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <USlideover v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-full m-4" />
    </template>
  </USlideover>
</template>
In this example, leveraging defineShortcuts, you can toggle the Slideover by pressing O.
This allows you to move the trigger outside of the Slideover or remove it entirely.

Programmatic usage

You can use the useSlideover composable to open a Slideover programatically.

Make sure to wrap your app with the App component which uses the SlideoverProvider component.

First, create a slideover component that will be opened programatically:

SlideoverExample.vue
<script setup lang="ts">
const slideover = useSlideover()

defineProps<{
  count: number
}>()
</script>

<template>
  <USlideover :description="`This slideover was opened programmatically ${count} times`">
    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <UButton color="neutral" label="Close" @click="slideover.close()" />
    </template>
  </USlideover>
</template>

Then, use it in your app:

<script setup lang="ts">
import { LazySlideoverExample } from '#components'

const count = ref(0)

const slideover = useSlideover()

function open() {
  count.value++

  slideover.open(LazySlideoverExample, {
    title: 'Slideover',
    count: count.value
  })
}
</script>

<template>
  <UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
You can close the slideover within the slideover component by calling slideover.close().

Nested slideovers

You can nest slideovers within each other.

<script setup lang="ts">
const first = ref(false)
const second = ref(false)
</script>

<template>
  <USlideover v-model:open="first" title="First slideover" :ui="{ footer: 'justify-end' }">
    <UButton color="neutral" variant="subtle" label="Open" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <UButton label="Close" color="neutral" variant="outline" @click="first = false" />

      <USlideover v-model:open="second" title="Second slideover" :ui="{ footer: 'justify-end' }">
        <UButton label="Open second" color="neutral" />

        <template #body>
          <Placeholder class="h-full" />
        </template>

        <template #footer>
          <UButton label="Close" color="neutral" variant="outline" @click="second = false" />
        </template>
      </USlideover>
    </template>
  </USlideover>
</template>

Use the #footer slot to add content after the Slideover's body.

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

<template>
  <USlideover v-model:open="open" title="Slideover with footer" description="This is useful when you want a form in a Slideover." :ui="{ footer: 'justify-end' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <UButton label="Cancel" color="neutral" variant="outline" @click="open = false" />
      <UButton label="Submit" color="neutral" />
    </template>
  </USlideover>
</template>

API

Props

Prop Default Type
title

string

description

string

content

DialogContentProps

The content of the slideover.

overlay

true

boolean

Render an overlay behind the slideover.

transition

true

boolean

Animate the slideover when opening or closing.

side

"right"

"left" | "right" | "top" | "bottom"

portal

true

boolean

Render the slideover in a portal.

close

true

boolean | ButtonProps

Display a close button to dismiss the slideover. { size: 'md', color: 'neutral', variant: 'ghost' }

closeIcon

appConfig.ui.icons.close

string

The icon displayed in the close button.

preventClose

false

boolean

When true, the slideover will not close when clicking outside.

open

boolean

The controlled open state of the dialog. Can be binded as v-model:open.

defaultOpen

boolean

The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.

modal

true

boolean

The modality of the dialog When set to true,
interaction with outside elements will be disabled and only dialog content will be visible to screen readers.

ui

Partial<{ overlay: string; content: string; header: string; body: string; footer: string; title: string; description: string; close: string; }>

Slots

Slot Type
default

{ open: boolean; }

content

{}

header

{}

title

{}

description

{}

close

{ ui: any; }

body

{}

footer

{}

Emits

Event Type
update:open

[value: boolean]

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    slideover: {
      slots: {
        overlay: 'fixed inset-0 bg-[var(--ui-bg-elevated)]/75',
        content: 'fixed bg-[var(--ui-bg)] divide-y divide-[var(--ui-border)] sm:ring ring-[var(--ui-border)] sm:shadow-lg flex flex-col focus:outline-none',
        header: 'px-4 py-5 sm:px-6',
        body: 'flex-1 overflow-y-auto p-4 sm:p-6',
        footer: 'flex items-center gap-1.5 p-4 sm:px-6',
        title: 'text-[var(--ui-text-highlighted)] font-semibold',
        description: 'mt-1 text-[var(--ui-text-muted)] text-sm',
        close: 'absolute top-4 end-4'
      },
      variants: {
        side: {
          top: {
            content: 'inset-x-0 top-0 max-h-full'
          },
          right: {
            content: 'right-0 inset-y-0 w-full max-w-md'
          },
          bottom: {
            content: 'inset-x-0 bottom-0 max-h-full'
          },
          left: {
            content: 'left-0 inset-y-0 w-full max-w-md'
          }
        },
        transition: {
          true: {
            overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]'
          }
        }
      },
      compoundVariants: [
        {
          transition: true,
          side: 'top',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-top_200ms_ease-in-out] data-[state=closed]:animate-[slide-out-to-top_200ms_ease-in-out]'
          }
        },
        {
          transition: true,
          side: 'right',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-right_200ms_ease-in-out] data-[state=closed]:animate-[slide-out-to-right_200ms_ease-in-out]'
          }
        },
        {
          transition: true,
          side: 'bottom',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-bottom_200ms_ease-in-out] data-[state=closed]:animate-[slide-out-to-bottom_200ms_ease-in-out]'
          }
        },
        {
          transition: true,
          side: 'left',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-left_200ms_ease-in-out] data-[state=closed]:animate-[slide-out-to-left_200ms_ease-in-out]'
          }
        }
      ]
    }
  }
})