Keyboard shortcut: Ctrl + K
Get started

Drawer

VDrawer opens a native modal panel against one edge of the viewport, and slides it in and out.

Usage

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCheckbox, VDrawer } from 'vectis-ui'

const open = ref(false)
const pending = ref(true)
const shipped = ref(false)
const refunded = ref(false)

function reset() {
  pending.value = false
  shipped.value = false
  refunded.value = false
}
</script>

<template>
  <VDrawer v-model:open="open" title="Filters" subtitle="Narrow the list of orders.">
    <template #trigger="{ triggerProps }">
      <VButton v-bind="triggerProps">Filters</VButton>
    </template>
    <div class="options">
      <VCheckbox v-model="pending">Pending</VCheckbox>
      <VCheckbox v-model="shipped">Shipped</VCheckbox>
      <VCheckbox v-model="refunded">Refunded</VCheckbox>
    </div>
    <template #footer>
      <VButton variant="ghost" tone="neutral" @click="reset">Reset</VButton>
      <VButton @click="open = false">Apply</VButton>
    </template>
  </VDrawer>
</template>

<style scoped>
.options {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-3);
}
</style>

Examples

Sides

side picks the edge. start and end follow the writing direction.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VDrawer, VTypography } from 'vectis-ui'
import type { DrawerSide } from 'vectis-ui'

const SIDES: DrawerSide[] = ['start', 'end', 'top', 'bottom']

const opened = ref<DrawerSide | null>(null)
</script>

<template>
  <div class="row">
    <VButton
      v-for="side in SIDES"
      :key="side"
      variant="outline"
      tone="neutral"
      @click="opened = side"
    >
      {{ side }}
    </VButton>
  </div>

  <VDrawer
    v-for="side in SIDES"
    :key="side"
    :side="side"
    :open="opened === side"
    :title="`From ${side}`"
    subtitle="The drawer slides in from this edge."
    @update:open="(value) => !value && (opened = null)"
  >
    <VTypography>
      In a right-to-left page, start is the right edge and end the left one.
    </VTypography>
  </VDrawer>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

Size

size sets the width of a drawer on a side, or the height of one at the top or bottom. extent replaces it with any CSS length. A strip of the page always stays uncovered.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VDrawer, VTypography } from 'vectis-ui'
import type { DrawerSize } from 'vectis-ui'

const SIZES: DrawerSize[] = ['sm', 'md', 'lg']

const opened = ref<string | null>(null)
</script>

<template>
  <div class="row">
    <VButton
      v-for="size in SIZES"
      :key="size"
      variant="outline"
      tone="neutral"
      @click="opened = size"
    >
      {{ size }}
    </VButton>
    <VButton variant="outline" tone="neutral" @click="opened = 'extent'">extent="50vw"</VButton>
  </div>

  <VDrawer
    v-for="size in SIZES"
    :key="size"
    :size="size"
    :open="opened === size"
    :title="`Size ${size}`"
    @update:open="(value) => !value && (opened = null)"
  >
    <VTypography>On a narrow screen the drawer leaves a strip of the page uncovered.</VTypography>
  </VDrawer>
  <VDrawer
    extent="50vw"
    :open="opened === 'extent'"
    title="Half the viewport"
    @update:open="(value) => !value && (opened = null)"
  >
    <VTypography>The extent is a CSS length, in any unit.</VTypography>
  </VDrawer>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

The body scrolls while the header and footer stay visible. Close the drawer when an item is selected.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VDrawer, VSideNavigation, VSideNavigationItem } from 'vectis-ui'

const open = ref(false)

const PAGES = [
  'Overview',
  'Activity',
  'Orders',
  'Invoices',
  'Customers',
  'Products',
  'Inventory',
  'Shipping',
  'Returns',
  'Discounts',
  'Reports',
  'Analytics',
  'Integrations',
  'Team',
  'Billing',
  'Settings',
]
</script>

<template>
  <VDrawer v-model:open="open" side="start" size="sm" title="Store">
    <template #trigger="{ triggerProps }">
      <VButton v-bind="triggerProps" variant="outline" tone="neutral">Menu</VButton>
    </template>
    <VSideNavigation label="Store">
      <VSideNavigationItem
        v-for="(page, index) in PAGES"
        :key="page"
        href="#navigation"
        :current="index === 0"
        @select="open = false"
      >
        {{ page }}
      </VSideNavigationItem>
    </VSideNavigation>
    <template #footer>
      <VButton variant="ghost" tone="neutral" @click="open = false">Sign out</VButton>
    </template>
  </VDrawer>
</template>

API

Props

PropTypeDefault
titlestringnone
Drawer title and accessible name. Ignored when the header slot is provided.
subtitlestringnone
Supporting text below the title.
sideDrawerSide'start' | 'end' | 'top' | 'bottom''end'
Edge the drawer comes from. start and end follow the writing direction.
sizeDrawerSize'sm' | 'md' | 'lg''md'
Width on a side, height at the top or bottom.
extentnumber | stringnone
Replaces size: pixels for numbers, otherwise a CSS length.
hideClosebooleanfalse
Hides the close button.
persistentBackdropbooleanfalse
Prevents outside clicks from closing the drawer.
persistentEscapebooleanfalse
Prevents Escape dismissal only with persistentBackdrop. Otherwise both dismissal routes stay enabled.
closeLabelstringnone
Accessible close button name. Defaults to the dictionary.
v-model:openbooleanfalse
Drawer open state. Native dismissal updates the model.

Slots

SlotType
default{}
Scrollable drawer body.
header{}
Custom header. Provide aria-label or aria-labelledby to name the drawer.
header-actions{}
Header controls before the close button.
footer{}
Drawer actions.
trigger{ triggerProps: DialogTriggerProps; }
Opening control. Bind the supplied triggerProps.

Types

Types used in the API tables. Import exported types from vectis-ui.

export type DialogTriggerProps = {
  onClick: () => void
  'aria-haspopup': 'dialog'
}

CSS variables

TokenValue
--vectis-control-size-drawer-sm20rem
--vectis-control-size-drawer-md25rem
--vectis-control-size-drawer-lg35rem