Drawer
A panel that slides up from the bottom of the screen on mobile devices.
Installation
dotnet add package Lumeo
One-time app setup (AddLumeo(), CSS & JS) is covered in the
installation guide.
Usage
@using Lumeo <Drawer />
position: fixed
popovers (Select, DatePicker, Combobox) inside Drawers. Fixed in rc.21
— the slide keyframes now end at transform: none
instead of identity-translate. If you still need predictability for older
rc consumers, set Animation="DrawerAnimation.Fade"
or Animation="DrawerAnimation.None".
<form>
placed directly inside DrawerContent
(instead of wrapped in a <div>)
used to collapse the whole panel to 0 height on the default Bottom/Top sides. Fixed — a
form child now renders with exactly the
same layout a div child would. See
"Drawer with a form" below.
When to Use
- Present mobile-first overlays that slide up from the bottom of the screen
- Build bottom sheets for action selection, filters, or confirmations
- Show supplementary content without navigating away from the current page
- Provide a gesture-friendly overlay that supports swipe-to-dismiss
- Display quick-action menus on touch devices
Bottom drawer with snap points at 40% / 75% / 100%. On a touch device (or DevTools device emulation), drag the handle to move between snaps; a quick flick down dismisses. On desktop it opens at the active snap so you can still see the resting position. Active snap: 75% (index 1).
Pick a value to see it applied live.
Sizes
Click any trigger below to open the drawer at that size — DrawerContent.DrawerSize covers every value. On
the default Bottom side, size controls the panel's max height; on Left/Right it controls max width instead
(the panel's own 3/4-viewport base width is unaffected — mirrors SheetContent.SheetSize.Full's
own Left/Right precedent). Full on
Top/Bottom takes the entire viewport, the mobile-fullscreen bottom-sheet pattern.
DrawerContent.DrawerSize
Intercepting close
OnBeforeClose fires for
every close path including swipe-down on touch devices. The handler receives a
DismissEventArgs with a
Reason string —
"escape",
"outside",
"swipe",
"close",
"action", or
"cancel". Set
e.Cancel = true to keep
the drawer open.
Nested stacking. Drawers stack automatically on top of Dialogs, Sheets, AlertDialogs, or other Drawers — z-index is allocated per overlay, and Escape closes only the top-most one. No API change.
Keyboard Interactions
| Key | Action |
|---|---|
| Escape | Close the drawer (unless PreventClose is enabled) |
| Tab | Cycle focus through focusable elements within the drawer |
| Shift + Tab | Move focus to the previous focusable element within the drawer |
On touch devices, swiping down on the drawer handle also dismisses it.
Exit animation
Closing a drawer plays a slide-and-fade-out animation and keeps the panel mounted until it finishes, so the
dismissal reads as deliberate rather than an abrupt pop. This is on by default. Set
PlayExitAnimation="false"
on DrawerContent to opt out and
unmount immediately — useful inside another animating container or for reduced-motion contexts.
Modal vs PreventClose
vaul separates two independent questions: modal —
does the drawer block the page (backdrop, focus trap, scroll lock)? — and
dismissible — can the user close
it at all (swipe, backdrop click, Escape)? Lumeo maps these onto two DrawerContent
parameters that compose freely:
| vaul | Lumeo | Default | Effect when set to the non-default value |
|---|---|---|---|
modal |
Modal |
true |
Modal="false": no backdrop, no focus trap, no scroll lock — the page stays fully interactive while the drawer is open. |
dismissible |
PreventClose (inverted) |
false |
PreventClose="true": Escape, backdrop-click and swipe are all disabled; the drawer only closes programmatically (or, in snap mode, settles at the lowest snap). |
The two compose freely — e.g. Modal="false"
with PreventClose="true" is a
drawer that never blocks the page AND can only be dismissed programmatically.
Built-in close button
ShowCloseButton renders a
top-end X, mirroring SheetContent.ShowCloseButton
exactly — same parameter name, same default coupling to PreventClose
(null), and the same override semantics: true forces the X on even when
PreventClose is set (it still routes
through the dismiss guard), false hides it for custom chrome. No more manual
DrawerClose required just to get an X.
Background scaling
Drawer.ScaleBackground (default
false) mirrors vaul's shouldScaleBackground —
while a Bottom drawer is open, the page behind it scales down, gains rounded corners, and nudges
down slightly, mimicking the native iOS "page recedes behind the sheet" effect. Top/Left/Right drawers never
trigger it, matching vaul's own bottom-sheet-only scope.
Requires marking your own wrapper element
Lumeo never renders the scaled element itself — mark your app's own page-root element (typically right inside<body>, wrapping your router/layout) with
data-lumeo-drawer-wrapper. With no element carrying that attribute,
ScaleBackground is a no-op — nothing breaks, nothing scales.
<!-- MainLayout.razor (or index.html's app root) -->
<div data-lumeo-drawer-wrapper>
<Router ...>
...
</Router>
</div>
<!-- Anywhere a bottom Drawer is opened: -->
<Drawer @bind-Open="_open" ScaleBackground="true">
<DrawerContent>
...
</DrawerContent>
</Drawer>API Reference
Drawer
DrawerContent
The panel that slides up from the bottom of the screen. Includes focus trap, scroll lock, and swipe-to-dismiss gesture. Sub-components DrawerTrigger, DrawerClose, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription mirror the Dialog/Sheet API.