Skip to content

Slideover ​

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

Usage ​

INFO

It should be understood that the Slideover component displays data using the SidebarLayout component.

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.

vue
<template>
  <B24Slideover
    :b24ui="{ content: 'sm:top-[300px] sm:max-h-[calc(100%-300px)]' }"
  >
    <B24Button label="Open" />

    <template #content>
      <Placeholder class="size-full bg-(--ui-color-base-7)" />
    </template>
  </B24Slideover>
</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.

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  title?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!'
})
</script>

<template>
  <B24Slideover
    :title="title"
  >
    <B24Button label="Open" />

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

Description ​

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

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

    <template #body>
      <Placeholder class="size-full" />
    </template>
  </B24Slideover>
</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 any property from the Button component to customize it.

TIP

The close button is not displayed if the #content slot is used as it's a part of the header.

Details
vue
<script setup lang="ts">
import type { ButtonProps } from '@bitrix24/b24ui-nuxt'

export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :title="title"
    :description="description"
    :close="{ label: 'Test', color: 'air-primary-copilot' } as ButtonProps"
    :b24ui="{ close: '-left-[1px]' }"
  >
    <B24Button label="Open with ai close button" />

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

Close Icon ​

Use the close-icon prop to customize the close button @bitrix24/b24icons. Defaults to Cross30Icon.

Details
vue
<script setup lang="ts">
import EyeClosedIcon from '@bitrix24/b24icons-vue/button/EyeClosedIcon'

export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :title="title"
    :description="description"
    :close-icon="EyeClosedIcon"
  >
    <B24Button label="Open" />

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

Side ​

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

Details
vue
<script setup lang="ts">
import type { SlideoverProps } from '@bitrix24/b24ui-nuxt'

export interface ExampleProps {
  side?: SlideoverProps['side']
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  side: 'bottom' as SlideoverProps['side'],
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :side="side"
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

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

Overlay ​

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

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  isOverlay?: boolean
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  isOverlay: true,
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :overlay="isOverlay"
    :title="title"
    :description="description"
    :b24ui="{ content: 'sm:top-[300px] sm:max-h-[calc(100%-300px)]' }"
  >
    <B24Button label="Open" />

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

If you want to disable background blur, you should use the overlayBlur prop. The overlayBlur prop has 3 options:

  • auto: when the user has not requested reduced motion
  • on: always use blur
  • off: (default) do not use blur
Details
vue
<script setup lang="ts">
import type { ModalProps } from '@bitrix24/b24ui-nuxt'

export interface ExampleProps {
  overlayBlur?: ModalProps['overlayBlur']
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  overlayBlur: 'auto' as ModalProps['overlayBlur'],
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Modal
    :overlay-blur="overlayBlur"
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

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

Transition ​

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

INFO

Reduced movement is taken into account

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  isTransition?: boolean
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  isTransition: true,
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :transition="isTransition"
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

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

Examples ​

Control open state ​

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

INFO

In this example, leveraging defineShortcuts, you can toggle the Slideover by pressing O.

TIP

This allows you to move the trigger outside of the Slideover or remove it entirely.

Details
vue
<script setup lang="ts">
import { ref } from 'vue'

export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})

const open = ref(false)

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

<template>
  <B24Slideover
    v-model:open="open"
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

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

Disable dismissal ​

Set the dismissible prop to false to prevent the Slideover from being closed when clicking outside of it or pressing escape. A close:prevent event will be emitted when the user tries to close it.

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :dismissible="false"
    :title="title"
    :description="description"
    :b24ui="{ content: 'sm:top-[300px] sm:max-h-[calc(100%-300px)]' }"
  >
    <B24Button label="Open" />

    <template #body>
      <ProseP accent="less-more" small>
        Slideover non-dismissible: <ProseCode color="air-primary-copilot">Esc</ProseCode> also doesn't work
      </ProseP>
    </template>
  </B24Slideover>
</template>

Programmatic usage ​

You can use the useOverlay composable to open a Slideover programmatically.

WARNING

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

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

vue
<script setup lang="ts">
defineProps<{
  count: number
}>()

const emit = defineEmits<{ close: [boolean] }>()
</script>

<template>
  <B24Slideover
    :close="{ onClick: () => emit('close', false) }"
    :title="`This Slideover was opened programmatically ${count} times`"
  >
    <template #body>
      <Placeholder class="size-full" />
    </template>

    <template #footer>
      <div class="flex flex-row gap-[10px]">
        <B24Button
          color="air-primary-success"
          label="Success"
          @click="emit('close', true)"
        />
        <B24Button
          label="Close"
          color="air-tertiary"
          @click="emit('close', false)"
        />
      </div>
    </template>
  </B24Slideover>
</template>

INFO

We are emitting a close event when the slideover is closed or dismissed here. You can emit any data through the close event, however, the event must be emitted in order to capture the return value.

Then, use it in your app:

TIP

You can close the slideover within the slideover component by emitting emit('close').

Details
vue
<script setup lang="ts">
import { ref } from 'vue'
import LazySlideover from './LazySlideover.vue'

const count = ref(0)

const toast = useToast()
const overlay = useOverlay()

const slideover = overlay.create(LazySlideover)

async function open() {
  const instance = slideover.open({
    count: count.value
  })

  const shouldIncrement = await instance.result

  if (shouldIncrement) {
    count.value++

    toast.add({
      title: `Success: ${shouldIncrement}`,
      color: 'air-primary-success',
      id: 'slideover-success'
    })

    // Update the count
    slideover.patch({
      count: count.value
    })
    return
  }

  toast.add({
    title: `Dismissed: ${shouldIncrement}`,
    color: 'air-primary-alert',
    id: 'slideover-dismiss'
  })
}
</script>

<template>
  <B24Button label="Open" @click="open" />
</template>

Nested slideovers ​

You can nest slideovers within each other.

Details
vue
<script setup lang="ts">
import { ref } from 'vue'

export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})

const first = ref(false)
const second = ref(false)
</script>

<template>
  <B24Slideover
    v-model:open="first"
    :title="title"
    :description="description"
  >
    <B24Button label="Open" />

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

    <template #footer>
      <div class="flex flex-row gap-[10px]">
        <B24Slideover
          v-model:open="second"
          title="Second Slideover"
          :b24ui="{ content: 'sm:top-[300px] sm:max-h-[calc(100%-300px)]' }"
        >
          <B24Button
            color="air-primary"
            label="Open second"
          />

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

          <template #footer>
            <B24Button
              color="air-tertiary"
              label="Close"
              @click="second = false"
            />
          </template>
        </B24Slideover>
        <B24Button
          label="Close"
          color="air-tertiary"
          @click="first = false"
        />
      </div>
    </template>
  </B24Slideover>
</template>

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

TIP

You can also close the dialog box using the B24ModalDialogClose component.

Details
vue
<script setup lang="ts">
export interface ExampleProps {
  title?: string
  description?: string
}

withDefaults(defineProps<ExampleProps>(), {
  title: 'Heads up!',
  description: 'Let\'s signal the manager that the deal is not moving.'
})
</script>

<template>
  <B24Slideover
    :title="title"
    :description="description"
    :b24ui="{ footer: 'justify-between' }"
  >
    <B24Button label="Open" />

    <template #body>
      <Placeholder class="size-full" />
    </template>
    <template #footer="{ close }">
      <div class="w-1/5 flex justify-start" />
      <div class="w-full flex flex-row justify-center gap-[10px]">
        <B24ModalDialogClose>
          <B24Button label="Send" color="air-primary" />
        </B24ModalDialogClose>
        <B24Button label="Cancel" color="air-tertiary" @click="close" />
      </div>
      <div class="w-1/5 flex justify-end">
        <B24Button label="Full version" size="sm" color="air-tertiary-no-accent" />
      </div>
    </template>
  </B24Slideover>
</template>

Simple list of elements ​

Details
vue
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import type { SlideoverInstance } from '@bitrix24/b24ui-nuxt'
import TrendUpIcon from '@bitrix24/b24icons-vue/outline/TrendUpIcon'
import TrendDownIcon from '@bitrix24/b24icons-vue/outline/TrendDownIcon'
import BusinesProcessStagesIcon from '@bitrix24/b24icons-vue/outline/BusinesProcessStagesIcon'

const openListItem = ref(false)

// Manage loading state
const currentSlideoverRef = useTemplateRef<SlideoverInstance>('currentSlideoverRef')
const handleAction = async () => {
  if (!currentSlideoverRef.value) {
    return
  }

  try {
    currentSlideoverRef.value.setSidebarLoading(true)
    await new Promise(resolve => setTimeout(resolve, 2_000))
  } finally {
    currentSlideoverRef.value.setSidebarLoading(false)
  }
}
</script>

<template>
  <B24Slideover
    ref="currentSlideoverRef"
    title="Some list"
    description="Some description"
    :use-light-content="false"
    :b24ui="{
      content: 'sm:max-w-[970px] sm:top-[275px] sm:max-h-[calc(100%-275px)]',
      sidebarLayoutRoot: [
        'edge-dark',
        'edge-dark:[--air-theme-bg-color:#7c235b]',
        'edge-dark:[--air-theme-bg-size:cover]',
        'edge-dark:[--air-theme-bg-repeat:no-repeat]',
        'edge-dark:[--air-theme-bg-position:0_0]',
        'edge-dark:[--air-theme-bg-attachment:local]',
        'edge-dark:[--air-theme-bg-image:url(/bg/edge-dark-v2.jpg)]',
        'edge-dark:[--air-theme-bg-image-blurred:url(/bg/edge-dark-v2-blurred.webp)]'
      ].join(' '),
      sidebarLayoutLoadingIcon: 'text-(--ui-color-gray-70)'
    }"
  >
    <B24Button label="Some list" />
    <template #body>
      <div class="light px-0.5 rounded-(--ui-border-radius-md) bg-(--ui-color-background-primary)">
        <B24TableWrapper
          row-hover
          class="overflow-x-auto w-full"
        >
          <table>
            <!-- head -->
            <thead>
              <tr>
                <th>#</th>
                <th>Company</th>
                <th>Status</th>
                <th>Amount (USD)</th>
              </tr>
            </thead>
            <tbody>
              <!-- row 1 -->
              <tr>
                <th>1</th>
                <td><B24Link @click="openListItem = true">Tech Innovators Inc.</B24Link></td>
                <td><B24Badge label="Proposal Sent" use-link use-close /></td>
                <td>50,000</td>
              </tr>
              <!-- row 2 -->
              <tr>
                <th>2</th>
                <td><B24Link @click="openListItem = true">Global Solutions Ltd.</B24Link></td>
                <td><B24Badge label="Negotiation" use-link inverted use-close /></td>
                <td>120,000</td>
              </tr>
              <!-- row 3 -->
              <tr>
                <th>3</th>
                <td><B24Link @click="openListItem = true">Future Enterprises</B24Link></td>
                <td><B24Chip standalone color="air-primary-warning" text="Contract Signed" size="lg" :trailing-icon="TrendUpIcon" /></td>
                <td>200,000</td>
              </tr>
              <!-- row 4 -->
              <tr>
                <th>4</th>
                <td><B24Link @click="openListItem = true">Bright Ideas Co.</B24Link></td>
                <td>
                  <B24Chip
                    standalone
                    text="Initial Contact"
                    color="air-primary-alert"
                    size="lg"
                    inverted
                    :trailing-icon="TrendDownIcon"
                  />
                </td>
                <td>15,000</td>
              </tr>
              <!-- row 5 -->
              <tr>
                <th>5</th>
                <td><B24Link @click="openListItem = true">NextGen Technologies</B24Link></td>
                <td>
                  <B24Chip
                    standalone
                    text="Important"
                    size="lg"
                    color="air-primary-alert"
                    :b24ui="{ base: 'style-filled-boost' }"
                  />
                </td>
                <td>300,000</td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <th colspan="3" class="text-right">
                  Total:
                </th>
                <td>
                  685,000
                </td>
              </tr>
            </tfoot>
          </table>
        </B24TableWrapper>
      </div>
    </template>

    <template #footer>
      <B24Button
        label="Reload"
        color="air-primary"
        loading-auto
        @click="handleAction"
      />
      <B24ModalDialogClose>
        <B24Button label="Cancel" color="air-tertiary" />
      </B24ModalDialogClose>
    </template>
  </B24Slideover>
  <B24Slideover
    v-model:open="openListItem"
    :close="{ label: 'Item' }"
    title="Item"
    description="Some description"
    :use-light-content="false"
    :b24ui="{
      content: 'sm:max-w-[965px] sm:top-[375px] sm:max-h-[calc(100%-375px)]',
      body: 'relative',
      sidebarLayoutRoot: [
        'edge-light',
        'edge-light:[--air-theme-bg-color:#eef2f4]',
        'edge-light:[--air-theme-bg-size:auto]',
        'edge-light:[--air-theme-bg-repeat:no-repeat]',
        'edge-light:[--air-theme-bg-position:0_0]',
        'edge-light:[--air-theme-bg-attachment:local]',
        'edge-light:[--air-theme-bg-image:url(/bg/slider-ring-blurred.webp)]',
        'edge-light:[--air-theme-bg-image-blurred:url(/bg/slider-ring-blurred.webp)]'
      ].join(' ')
    }"
  >
    <template #header>
      <div
        class="w-full pt-(--ui-space-inset-md2) pb-[calc(var(--ui-space-inset-md2)+10px)] px-(--ui-space-inset-lg) rounded-(--ui-border-radius-3xl) bg-(--ui-color-background-primary)/80 flex flex-row items-center justify-between gap-[20px]"
      >
        <B24Avatar
          :icon="BusinesProcessStagesIcon"
          alt="Workflows"
          size="2xl"
          :b24ui="{
            root: 'bg-(--ui-color-primary)',
            icon: 'size-[48px] text-(--ui-color-palette-white-base)'
          }"
        />
        <div class="flex-1">
          <ProseH1 class="text-(--ui-color-text-primary) leading-[29px] font-(--ui-font-weight-light)">
            Workflows
          </ProseH1>
          <ProseP small accent="less">
            Automate your workflows, control every stage and manage workflows from your mobile.
          </ProseP>
        </div>
      </div>
    </template>
    <template #body>
      <Placeholder class="w-full h-[300px]" />
    </template>
    <template #footer>
      <B24ModalDialogClose>
        <B24Button label="Continue" color="air-primary" />
      </B24ModalDialogClose>
      <B24ModalDialogClose>
        <B24Button label="Back" color="air-tertiary" />
      </B24ModalDialogClose>
    </template>
  </B24Slideover>
</template>

TIP

Many examples can be found on the playground and also seen in the demo version.

API ​

Props ​

Prop Default Type
titlestring
descriptionstring
contentOmit<DialogContentProps, "asChild" | "as" | "forceMount"> & Partial<EmitsToProps<DialogContentImplEmits>>
The content of the slideover
overlaytrueboolean
Render an overlay behind the slideover.
overlayBlur"off""off" | "auto" | "on"
Render an overlay blur behind the slideover. `auto` use `motion-safe`.
transitiontrueboolean
Animate the slideover when opening or closing.
side"bottom""top" | "bottom" | "right" | "left"
The side of the slideover.
portaltruestring | false | true | HTMLElement
Render the slideover in a portal.
closetrueboolean | Partial<ButtonProps>
Display a close button to dismiss the slideover. `{ color: 'air-primary' }`{lang="ts"} for `left`, `right`, `bottom` `{ color: 'air-tertiary' }`{lang="ts"} for `top`
closeIconicons.close(props: HTMLAttributes & VNodeProps & {}, ctx: Omit<{ attrs: Data; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<...>>(exposed?: Exposed) => void; }, "expose">): any
The icon displayed in the close button.
dismissibletrueboolean
When `false`, the slideover will not close when clicking outside or pressing escape.
useLightContenttrueboolean
The content is placed on a light background.
openboolean
The controlled open state of the dialog. Can be binded as `v-model:open`.
defaultOpenboolean
The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.
modaltrueboolean
The modality of the dialog When set to `true`, <br> interaction with outside elements will be disabled and only dialog content will be visible to screen readers.
b24ui{ overlay?: ClassNameValue; content?: ClassNameValue; sidebarLayoutRoot?: ClassNameValue; sidebarLayoutHeaderWrapper?: ClassNameValue; sidebarLayoutPageBottomWrapper?: ClassNameValue; sidebarLayoutLoadingWrapper?: ClassNameValue; sidebarLayoutLoadingIcon?: ClassNameValue; header?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; close?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; safeList?: ClassNameValue; }

Slots ​

Slot Type
default{ open: boolean; }
content{ close: () => void; }
sidebar{ close: () => void; }
navbar{ close: () => void; }
header{ close: () => void; }
title{}
description{}
actions{}
close{ close: () => void; b24ui: { overlay: (props?: Record<string, any>) => string; content: (props?: Record<string, any> | undefined) => string; sidebarLayoutRoot: (props?: Record<string, any> | undefined) => string; sidebarLayoutHeaderWrapper: (props?: Record<string, any> | undefined) => string; sidebarLayoutPageBottomWrapper: (props?: Record<string, any> | undefined) => string; sidebarLayoutLoadingWrapper: (props?: Record<string, any> | undefined) => string; sidebarLayoutLoadingIcon: (props?: Record<string, any> | undefined) => string; header: (props?: Record<string, any> | undefined) => string; wrapper: (props?: Record<string, any> | undefined) => string; title: (props?: Record<string, any> | undefined) => string; description: (props?: Record<string, any> | undefined) => string; close: (props?: Record<string, any> | undefined) => string; body: (props?: Record<string, any> | undefined) => string; footer: (props?: Record<string, any> | undefined) => string; safeList: (props?: Record<string, any> | undefined) => string; }; }
body{ close: () => void; }
footer{ close: () => void; }

Emits ​

ts
/**
 * Emitted events for the Slideover component
 */
interface SlideoverEmits {
  update:open: (payload: [value: boolean]) => void;
  after:leave: (payload: []) => void;
  after:enter: (payload: []) => void;
  close:prevent: (payload: []) => void;
}

Released under the MIT License.