Component
drawer
dx components add drawerA drawer component -- a Vaul-style sheet with drag-to-dismiss, sliding in from an edge of the screen
Installation
Use the CLI command for the common path, or copy the component files manually.
Manual installation files
Copy the component source and CSS into your app. Import the shared theme CSS once near your app root.
use dioxus::prelude::*;
use dioxus_primitives::dialog::{DialogDescriptionProps, DialogTitleProps};
use dioxus_primitives::dioxus_attributes::attributes;
use dioxus_primitives::drawer::{
self, DrawerCloseProps, DrawerContentProps, DrawerHandleProps, DrawerRootProps,
};
use dioxus_primitives::merge_attributes;
pub use dioxus_primitives::drawer::DrawerSide;
#[component]
pub fn Drawer(props: DrawerRootProps) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
drawer::Drawer {
class: "dx-drawer-root",
"data-slot": "drawer-root",
id: props.id,
is_modal: props.is_modal,
open: props.open,
default_open: props.default_open,
on_open_change: props.on_open_change,
side: props.side,
dismissible: props.dismissible,
{props.children}
}
}
}
#[component]
pub fn DrawerContent(props: DrawerContentProps) -> Element {
let content_base = attributes!(div {
class: "dx-drawer",
"data-slot": "drawer-content",
});
let content_attributes = merge_attributes(vec![content_base, props.attributes]);
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
drawer::DrawerContent {
id: props.id,
class: None,
attributes: content_attributes,
{props.children}
}
}
}
#[component]
pub fn DrawerHandle(props: DrawerHandleProps) -> Element {
let base = attributes!(div {
class: "dx-drawer-handle",
"data-slot": "drawer-handle",
});
let merged = merge_attributes(vec![base, props.attributes]);
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
drawer::DrawerHandle { attributes: merged }
}
}
#[component]
pub fn DrawerHeader(
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
div { class: "dx-drawer-header", "data-slot": "drawer-header", ..attributes, {children} }
}
}
#[component]
pub fn DrawerFooter(
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
div { class: "dx-drawer-footer", "data-slot": "drawer-footer", ..attributes, {children} }
}
}
#[component]
pub fn DrawerTitle(props: DialogTitleProps) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
drawer::DrawerTitle {
id: props.id,
class: "dx-drawer-title",
"data-slot": "drawer-title",
attributes: props.attributes,
{props.children}
}
}
}
#[component]
pub fn DrawerDescription(props: DialogDescriptionProps) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("/src/components/drawer/style.css") }
drawer::DrawerDescription {
id: props.id,
class: "dx-drawer-description",
"data-slot": "drawer-description",
attributes: props.attributes,
{props.children}
}
}
}
// No `document::Link` here (unlike this file's other exported components):
// `DrawerClose` reads `DialogCtx` via `use_context()` (inside the primitive
// it wraps), so it can only ever render as a descendant of a `Drawer` --
// and in this file that context is provided by `Drawer` alone, which
// already links the drawer stylesheet. Mirrors
// `preview/src/components/sheet/component.rs`'s `SheetClose`'s identical
// reasoning.
#[component]
pub fn DrawerClose(props: DrawerCloseProps) -> Element {
let base = attributes!(button {
class: "dx-drawer-close",
});
let merged = merge_attributes(vec![base, props.attributes]);
rsx! {
drawer::DrawerClose { attributes: merged, r#as: props.r#as, {props.children} }
}
}Usage notes
The drawer component is a panel that slides in from the edge of the screen, like the sheet component, but adds a pointer drag gesture (a Vaul-style "swipe to dismiss") on top -- drag its handle or content toward the edge it slid in from to close it, in addition to Escape or a close button.
Component Structure
Drawer {
open: open(),
// Which edge to slide in from. Available sides: Top, Right, Bottom (default), Left.
side: DrawerSide::Bottom,
DrawerContent {
DrawerHandle {}
DrawerHeader {
DrawerTitle { "Move Goal" }
DrawerDescription { "Set your daily activity goal." }
}
DrawerFooter {
DrawerClose { "Submit" }
DrawerClose {
as: |attributes| rsx! {
Button { variant: ButtonVariant::Outline, attributes, "Cancel" }
}
}
}
}
}Drag to dismiss
Dragging [DrawerHandle] or anywhere on [DrawerContent] itself toward side tracks the pointer live. Releasing past 25% of the panel's own size along that axis, or with enough velocity, finishes closing the drawer; releasing short of that snaps it back open. Set dismissible: false on Drawer to disable the drag gesture entirely while keeping Escape and DrawerClose working.
A drag never starts from an interactive descendant (a button, link, or form control), or from content that is scrolled away from the drag edge -- either is read as "interact with (or scroll) this instead," not "dismiss the drawer."
DrawerClose with as prop
The as prop allows you to render a custom element while preserving the close behavior, similar to shadcn/ui's asChild pattern.
// Default: renders as <button>
DrawerClose { "Close" }
// Custom element: attributes include the preset onclick handler
DrawerClose {
as: |attributes| rsx! {
a { href: "#", ..attributes, "Go back" }
}
}