MagicDrawer
MagicDrawer is a flexible, touch enabled, unstyled drawer component. Useful for things like shopping carts, menus, as a modal replacement on mobile devices and the like.
<template>
<m-button @click="drawerApi.open">Open Drawer</m-button>
<magic-drawer id="magic-drawer-demo" :options="{ focusTrap: false }">
<div class="bg-surface-base absolute inset-0" />
</magic-drawer>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const drawerApi = useMagicDrawer('magic-drawer-demo')
</script>Overview
Anatomy
MagicDrawer can be used as a single self-contained component or composed from its individual parts for full control.
Simple
<template>
<magic-drawer id="your-drawer-id">
<!-- your content -->
</magic-drawer>
</template>
<script setup>
const { open } = useMagicDrawer('your-drawer-id')
</script>Composed
<template>
<magic-drawer-provider id="your-drawer-id">
<magic-drawer-trigger as-child>
<button>Open</button>
</magic-drawer-trigger>
<magic-drawer-teleport>
<magic-drawer-backdrop />
<magic-drawer-content>
<!-- your content -->
</magic-drawer-content>
</magic-drawer-teleport>
</magic-drawer-provider>
</template>TIP
MagicDrawerTeleport is optional. Omit it to keep the drawer mounted at all times, with visibility controlled via v-show.
Installation
CLI
Add @maas/vue-equipment to your dependencies.
pnpm install @maas/vue-equipmentnpm install @maas/vue-equipmentyarn add @maas/vue-equipmentbun install @maas/vue-equipmentVue
If you are using Vue, import and add MagicDrawerPlugin to your app.
import { createApp } from 'vue'
import { MagicDrawerPlugin } from '@maas/vue-equipment/plugins/MagicDrawer'
const app = createApp({})
app.use(MagicDrawerPlugin)Nuxt
The drawer is available as a Nuxt module. In your Nuxt config file add @maas/vue-equipment/nuxt to your modules and add MagicDrawer to the plugins in your configuration.
export default defineNuxtConfig({
modules: ['@maas/vue-equipment/nuxt'],
vueEquipment: {
plugins: ['MagicDrawer'],
},
})Direct Import
If you prefer a more granular approach, components can be directly imported.
<script setup>
import {
MagicDrawerProvider,
MagicDrawerTeleport,
MagicDrawerBackdrop,
MagicDrawerContent,
MagicDrawerTrigger,
} from '@maas/vue-equipment/plugins/MagicDrawer'
</script>Composable
In order to interact with the drawer from anywhere within your app, we provide a useMagicDrawer composable. Import it directly when needed.
import { onMounted } from 'vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const { open } = useMagicDrawer('your-drawer-id')
onMounted(() => {
open()
})TIP
If you have installed the drawer as a Nuxt module, the composable will be auto-imported and is automatically available in your Nuxt app.
Peer Dependencies
If you haven't installed the required peer dependencies automatically, you'll need to install the following packages manually.
Installation
pnpm install @nuxt/kit @vueuse/core @vueuse/integrations defu focus-trap wheel-gesturesnpm install @nuxt/kit @vueuse/core @vueuse/integrations defu focus-trap wheel-gesturesyarn add @nuxt/kit @vueuse/core @vueuse/integrations defu focus-trap wheel-gesturesbun install @nuxt/kit @vueuse/core @vueuse/integrations defu focus-trap wheel-gesturesAPI Reference
MagicDrawerProvider
The MagicDrawerProvider wraps the drawer and configures all child components according to the provided options.
Props
| Prop | Type | Required |
|---|---|---|
MaybeRef<string> | true | |
MagicDrawerOptions | false |
Options
To customize the drawer, override the necessary options. Any custom options will be merged with the default options.
| Option | Type | Default |
|---|---|---|
'bottom' | ||
'dialog' | ||
boolean | FocusTrapOptions | object | |
boolean | object | object | |
boolean | true | |
[1] | ||
string | 'body' | |
boolean | false | |
string | 'magic-drawer-content' | |
string | 'magic-drawer-backdrop' | |
number | 0 | |
number | 128 | |
number | 1 | |
number | 300 | |
function | ||
boolean | false | |
boolean | — | |
— | ||
string[] | false | ['Escape'] | |
boolean | false | |
boolean | true | |
boolean | false | |
boolean | false |
MagicDrawerTeleport
Teleports all child components to a target in the DOM. Uses v-if to mount and unmount its contents when the drawer opens and closes — keeping the DOM clean when the drawer is inactive. Omit this component if you want the drawer to remain mounted at all times.
Props
| Prop | Type | Required |
|---|---|---|
string | RendererElement | false | |
boolean | false |
MagicDrawerBackdrop
Renders a full-viewport overlay behind the drawer panel. Closes the drawer when clicked.
CSS Variables
| Variable | Default |
|---|---|
--magic-drawer-backdrop-position | fixed |
--magic-drawer-backdrop-top | 0 |
--magic-drawer-backdrop-left | 0 |
--magic-drawer-backdrop-right | 0 |
--magic-drawer-backdrop-bottom | 0 |
--magic-drawer-backdrop-width | 100% |
--magic-drawer-backdrop-height | 100% |
--magic-drawer-backdrop-color | rgba(0, 0, 0, 0.5) |
--magic-drawer-backdrop-filter | unset |
--magic-drawer-backdrop-z-index | 998 |
MagicDrawerContent
Renders the drawer panel with drag, snap, and scroll behavior.
CSS Variables
| Variable | Default |
|---|---|
--magic-drawer-position | fixed |
--magic-drawer-inset | 0 |
--magic-drawer-display | flex |
--magic-drawer-z-index | 999 |
--magic-drawer-height | 75svh |
--magic-drawer-width | 100% |
--magic-drawer-max-height | none |
--magic-drawer-max-width | none |
--magic-drawer-content-width | 100% |
--magic-drawer-content-height | 100% |
--magic-drawer-content-max-height | 100% |
--magic-drawer-justify-content | center |
--magic-drawer-align-items | flex-end |
--magic-drawer-enter-animation | slide-btt-in 300ms ease |
--magic-drawer-leave-animation | slide-btt-out 300ms ease |
--magic-drawer-drag-overshoot | 4rem |
--magic-drawer-cursor | grab |
--magic-drawer-cursor-dragging | grabbing |
MagicDrawerTrigger
Opens or closes the drawer on click.
Props
| Prop | Type | Required |
|---|---|---|
MaybeRef<string> | false | |
boolean | false | |
boolean | false |
Slot Props
| Prop | Type | Description |
|---|---|---|
active | boolean | Whether the drawer is currently open. |
disabled | boolean | Whether the trigger is currently disabled. |
MagicDrawer
A self-contained component that composes all primitives internally. Use this for simple cases where you don’t need to customise the markup.
Props
| Prop | Type | Required |
|---|---|---|
MaybeRef<string> | true | |
MagicDrawerOptions | false |
Slots
| Slot | Description |
|---|---|
default | Content rendered inside the drawer panel. |
backdrop | Content rendered inside the backdrop element. |
Errors
| Source | Error Code | Message |
|---|---|---|
MagicDrawerTeleport | missing_instance_id | MagicDrawerTeleport must be nested inside MagicDrawerProvider |
MagicDrawerBackdrop | missing_instance_id | MagicDrawerBackdrop must be nested inside MagicDrawerProvider |
MagicDrawerContent | missing_instance_id | MagicDrawerContent must be nested inside MagicDrawerProvider |
MagicDrawerTrigger | missing_instance_id | MagicDrawerTrigger must be nested inside MagicDrawerProvider or an id must be provided |
MagicDrawerContent | overshoot_unit | --magic-drawer-drag-overshoot needs to be specified in px or rem |
Caveats
The drawer handles situations where dragging and scrolling might interfere with each other on touch devices. In order for the drawer to differentiate when the user scrolls and when the user drags, any scrollable containers within the drawer need to have their overflow value explicitly set to auto or scroll.
Examples
Vertical
<template>
<m-button @click="drawerApi.open">Open Drawer</m-button>
<magic-drawer id="magic-drawer-vertical-demo" :options="{ focusTrap: false }">
<div class="bg-surface-base absolute inset-0" />
</magic-drawer>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const drawerApi = useMagicDrawer('magic-drawer-vertical-demo')
</script>Horizontal
<template>
<m-button @click="drawerApi.open">Open Drawer</m-button>
<magic-drawer
id="magic-drawer-horizontal-demo"
:options="{ focusTrap: false, position: 'right' }"
>
<div class="bg-surface-base absolute inset-0" />
</magic-drawer>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const drawerApi = useMagicDrawer('magic-drawer-horizontal-demo')
</script>
<style>
[data-id='magic-drawer-horizontal-demo'] {
--magic-drawer-height: 100svh;
--magic-drawer-width: 20rem;
}
</style>Snap Points
<template>
<m-button @click="drawerApi.open">Open Drawer</m-button>
<magic-drawer
id="magic-drawer-snap-points-demo"
:options="{
focusTrap: false,
snapPoints: snapPoints,
initial: { snapPoint: snapPoints[0] },
}"
>
<div class="bg-surface-base absolute inset-0" />
</magic-drawer>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const snapPoints = ['320px', 0.75, 1]
const drawerApi = useMagicDrawer('magic-drawer-snap-points-demo')
</script>
<style>
[data-id='magic-drawer-snap-points-demo'] {
--magic-drawer-height: 100svh;
}
</style>Mousewheel
<template>
<m-button @click="drawerApi.open">Open Drawer</m-button>
<magic-drawer
id="magic-drawer-mousewheel-demo"
:options="{ focusTrap: false, enableMousewheel: true }"
>
<div class="bg-surface-base absolute inset-0" />
</magic-drawer>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import { useMagicDrawer } from '@maas/vue-equipment/plugins/MagicDrawer'
const drawerApi = useMagicDrawer('magic-drawer-mousewheel-demo')
</script>Composed
<template>
<magic-drawer-provider id="magic-drawer-composed-demo" :options="{ focusTrap: false }">
<magic-drawer-trigger as-child>
<m-button>Open Drawer</m-button>
</magic-drawer-trigger>
<magic-drawer-teleport>
<magic-drawer-backdrop />
<magic-drawer-content>
<div class="bg-surface-base absolute inset-0" />
</magic-drawer-content>
</magic-drawer-teleport>
</magic-drawer-provider>
</template>
<script lang="ts" setup>
import { MButton } from '@maas/mirror/vue'
import {
MagicDrawerProvider,
MagicDrawerTeleport,
MagicDrawerBackdrop,
MagicDrawerContent,
MagicDrawerTrigger,
} from '@maas/vue-equipment/plugins/MagicDrawer'
</script>