A Drawer for Rust. Inspired by the amazing work of Emil Kowalski.
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Drawer Title
Drag down to close or click outside.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerTitle, DrawerTrigger,
};
#[component]
pub fn DemoDrawer() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent>
<DrawerHandle />
<DrawerBody class="justify-center items-center">
<DrawerTitle>Drawer Title</DrawerTitle>
<DrawerDescription>Drag down to close or click outside.</DrawerDescription>
<DrawerClose>Close</DrawerClose>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}
Installation
# Coming soon :)
Usage
// Coming soon 🦀
Examples
Family
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Options
use icons::{FileText, Lock, TriangleAlert, X};
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{Drawer, DrawerContent, DrawerTrigger};
#[component]
pub fn DemoDrawerFamily() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent class="overflow-hidden right-4 left-4 pb-6 mx-auto mb-4 max-w-[361px] rounded-[36px]">
// <!-- Content -->
<header class="flex justify-between items-center mb-4 border-b h-[72px] border-neutral-100">
<h2 class="text-lg font-semibold text-foreground">Options</h2>
<button
data-name="DrawerClose"
class="flex justify-center items-center rounded-full transition-colors size-8 bg-neutral-100 text-neutral-500 hover:bg-neutral-200"
>
<X class="size-3" />
</button>
</header>
<div class="space-y-3">
<button class="flex gap-4 items-center px-4 w-full h-12 text-base font-medium rounded-2xl transition-colors bg-neutral-100 text-foreground hover:bg-neutral-200">
<Lock class="size-[18px]" />
<span>View Private Key</span>
</button>
<button class="flex gap-4 items-center px-4 w-full h-12 text-base font-medium rounded-2xl transition-colors bg-neutral-100 text-foreground hover:bg-neutral-200">
<FileText class="size-[18px]" />
<span>View Recovery Phase</span>
</button>
<button class="flex gap-4 items-center px-4 w-full h-12 text-base font-medium text-red-600 bg-red-50 rounded-2xl transition-colors hover:bg-red-100">
<TriangleAlert class="size-[18px]" />
<span>Remove Wallet</span>
</button>
</div>
</DrawerContent>
</Drawer>
}
}
Focus
- Copy Demo
Focus Drawer Demo
Click the button below to open the drawer
Drawer Title
Test keyboard navigation: Press Tab to cycle through elements, Shift+Tab to go back, and Escape to close.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerTitle, DrawerTrigger,
};
use crate::components::ui::input::Input;
use crate::components::ui::label::Label;
use crate::components::ui::textarea::Textarea;
#[component]
pub fn DemoDrawerFocus() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Focus Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent>
<DrawerHandle />
<DrawerBody>
<DrawerTitle class="text-center">Drawer Title</DrawerTitle>
<DrawerDescription>
Test keyboard navigation: Press Tab to cycle through elements, Shift+Tab to go back, and Escape to close.
</DrawerDescription>
// <!-- Form elements for testing tab trapping -->
<form class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<Label html_for="test-input">Text Input</Label>
<Input attr:id="test-input" attr:placeholder="Type something..." />
</div>
<div class="flex flex-col gap-2">
<Label html_for="test-email">Email</Label>
<Input attr:id="test-email" attr:r#type="email" attr:placeholder="[email protected]" />
</div>
<div class="flex flex-col gap-2">
<Label html_for="test-select">Select</Label>
<select
id="test-select"
class="py-2 px-3 w-full rounded-md border focus:ring-2 focus:outline-none border-neutral-200 focus:ring-primary"
>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
<div class="flex flex-col gap-2">
<Label html_for="test-textarea">Textarea</Label>
<Textarea attr:id="test-textarea" attr:rows="4" attr:placeholder="Write a comment..." />
</div>
</form>
<DrawerClose>Close</DrawerClose>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}
Nested
- Copy Demo
Nested Drawer Demo
Click the button below to open the drawer
Parent Drawer
Click the button below to open a nested drawer and see the parent drawer scale down.
Nested Drawer
Notice how the parent drawer scales down when this nested drawer opens.
use leptos::prelude::*;
use leptos_meta::Stylesheet;
use crate::components::_coming_soon::drawer::DrawerHandle;
#[component]
pub fn DemoDrawerNested() -> impl IntoView {
view! {
// <Stylesheet href="/drawer.css" />
<Stylesheet href="/drawer_nested.css" />
<div>
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Nested Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<button
id="drawer-trigger"
class="py-3 px-6 font-medium rounded-lg transition-opacity hover:opacity-90 bg-primary text-primary-foreground"
>
Open Drawer
</button>
</div>
// <!-- Drawer Overlay -->
<div
id="drawer-overlay"
class="hidden drawer-overlay"
data-vaul-overlay=""
data-vaul-snap-points="false"
data-vaul-animate="true"
data-state="closed"
></div>
// <!-- Drawer Content -->
<div
id="drawer-content"
class="hidden bg-white drawer-content rounded-t-[10px]"
data-vaul-drawer=""
data-vaul-drawer-direction="bottom"
data-vaul-snap-points="false"
data-vaul-animate="true"
data-state="closed"
style="--initial-transform: 100%;"
>
// <!-- Drawer Body -->
<div class="flex overflow-y-auto flex-col flex-1 gap-4 p-6">
// <!-- Handle inside DrawerBody -->
<DrawerHandle />
<h2 class="text-2xl font-bold text-foreground">Parent Drawer</h2>
<p class="text-muted-foreground">
Click the button below to open a nested drawer and see the parent drawer scale down.
</p>
<button
id="open-nested-drawer"
class="py-2 px-4 w-full font-medium rounded-md transition-opacity hover:opacity-90 focus:ring-2 focus:ring-offset-2 focus:outline-none bg-secondary text-secondary-foreground focus:ring-secondary"
>
Open Nested Drawer
</button>
<button
id="drawer-close"
class="py-2 px-4 w-full font-medium rounded-md transition-opacity hover:opacity-90 focus:ring-2 focus:ring-offset-2 focus:outline-none bg-primary text-primary-foreground focus:ring-primary"
>
Close
</button>
</div>
</div>
// <!-- Nested Drawer Overlay -->
<div
id="nested-drawer-overlay"
class="hidden drawer-overlay overlay-nested"
data-vaul-overlay=""
data-vaul-snap-points="false"
data-vaul-animate="true"
data-state="closed"
></div>
// <!-- Nested Drawer Content -->
<div
id="nested-drawer-content"
class="hidden bg-white drawer-content drawer-nested rounded-t-[10px]"
data-vaul-drawer=""
data-vaul-drawer-direction="bottom"
data-vaul-snap-points="false"
data-vaul-animate="true"
data-state="closed"
style="--initial-transform: 100%;"
>
// <!-- Nested Drawer Body -->
<div class="flex overflow-y-auto flex-col flex-1 gap-4 p-6">
// <!-- Handle inside DrawerBody -->
<DrawerHandle />
<h2 class="text-2xl font-bold text-foreground">Nested Drawer</h2>
<p class="text-muted-foreground">
Notice how the parent drawer scales down when this nested drawer opens.
</p>
<button
id="nested-drawer-close"
class="py-2 px-4 w-full font-medium rounded-md transition-opacity hover:opacity-90 focus:ring-2 focus:ring-offset-2 focus:outline-none bg-primary text-primary-foreground focus:ring-primary"
>
Close Nested Drawer
</button>
</div>
</div>
</div>
<script type="module" src="/drawer_v3.js"></script>
}
}
Non Dismissable
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Are you absolutely sure?
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerTitle, DrawerTrigger,
};
#[component]
pub fn DemoDrawerNonDismissable() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent style="--initial-transform: 100%; pointer-events: auto;" dismissible="false">
<DrawerHandle />
<DrawerBody>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
</DrawerDescription>
<DrawerClose>Confirm</DrawerClose>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}
Scrollable
- Copy Demo
Scrollable Drawer Demo
Click the button below to open the drawer
Ira Glass on Taste
Nobody tells this to people who are beginners, I wish someone told me. All of us who do creative work, we get into it because we have good taste.
But there is this gap. For the first couple years you make stuff, it's just not that good. It's trying to be good, it has potential, but it's not. But your taste, the thing that got you into the game, is still killer. And your taste is why your work disappoints you. A lot of people never get past this phase, they quit.
Most people I know who do interesting, creative work went through years of this. We know our work doesn't have this special thing that we want it to have. We all go through this. And if you are just starting out or you are still in this phase, you gotta know its normal and the most important thing you can do is do a lot of work
Put yourself on a deadline so that every week you will finish one story. It is only by going through a volume of work that you will close that gap, and your work will be as good as your ambitions. And I took longer to figure out how to do this than anyone I've ever met. It's gonna take awhile. It's normal to take awhile. You've just gotta fight your way through.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerTitle, DrawerTrigger,
};
#[component]
pub fn DemoDrawerScrollable() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Scrollable Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent style="--initial-transform: 100%; pointer-events: auto;">
<DrawerHandle />
<DrawerBody class="overflow-y-auto pr-4 max-h-[300px]">
<DrawerTitle class="text-center">Ira Glass on Taste</DrawerTitle>
<DrawerDescription>
Nobody tells this to people who are beginners, I wish someone told me. All of us who do creative work, we get into it because we have good taste.
</DrawerDescription>
<DrawerDescription>
"But there is this gap. For the first couple years you make stuff, it's just not that good. It's trying to be good, it has potential, but it's not. But your taste, the thing that got you into the game, is still killer. And your taste is why your work disappoints you. A lot of people never get past this phase, they quit."
</DrawerDescription>
<DrawerDescription>
" Most people I know who do interesting, creative work went through years of this. We know our work doesn't have this special thing that we want it to have. We all go through this. And if you are just starting out or you are still in this phase, you gotta know its normal and the most important thing you can do is do a lot of work"
</DrawerDescription>
<DrawerDescription>
" Put yourself on a deadline so that every week you will finish one story. It is only by going through a volume of work that you will close that gap, and your work will be as good as your ambitions. And I took longer to figure out how to do this than anyone I've ever met. It's gonna take awhile. It's normal to take awhile. You've just gotta fight your way through."
</DrawerDescription>
<DrawerClose>Close</DrawerClose>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}
Side
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Different Directions
It supports all directions.
This one specifically is not touching the edge of the screen, but that is not required for a side drawer.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerPosition, DrawerTitle, DrawerTrigger,
};
#[component]
pub fn DemoDrawerSide() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent
position=DrawerPosition::Right
class="top-0 bottom-0 left-auto h-full max-h-full rounded-t-none w-[300px] rounded-l-[10px]"
>
<DrawerBody>
<DrawerTitle class="mt-4 text-base">Different Directions</DrawerTitle>
<DrawerDescription>It supports all directions.</DrawerDescription>
<DrawerDescription>
This one specifically is not touching the edge of the screen, but that is not required for a side drawer.
</DrawerDescription>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}
Side Floating
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Different Directions
It supports all directions.
This one specifically is not touching the edge of the screen, but that is not required for a side drawer.
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerContent, DrawerDescription, DrawerPosition, DrawerTitle, DrawerTrigger, DrawerVariant,
};
#[component]
pub fn DemoDrawerSideFloating() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent
position=DrawerPosition::Right
variant=DrawerVariant::Floating
class="top-2 right-2 bottom-2 left-auto max-h-full outline-none w-[300px] rounded-[16px] rounded-t-[16px]"
style="--initial-transform: calc(100% + 8px);"
>
<DrawerTitle class="mt-4 text-base">Different Directions</DrawerTitle>
<DrawerDescription>It supports all directions.</DrawerDescription>
<DrawerDescription>
This one specifically is not touching the edge of the screen, but that is not required for a side drawer.
</DrawerDescription>
</DrawerContent>
</Drawer>
}
}
Side Scrollable
- Copy Demo
Vaul Drawer Demo
Click the button below to open the drawer
Scrollable Side Drawer
This drawer contains 50 scrollable items to demonstrate side scrolling behavior.
Item 1
This is item number 1in the list
Item 2
This is item number 2in the list
Item 3
This is item number 3in the list
Item 4
This is item number 4in the list
Item 5
This is item number 5in the list
Item 6
This is item number 6in the list
Item 7
This is item number 7in the list
Item 8
This is item number 8in the list
Item 9
This is item number 9in the list
Item 10
This is item number 10in the list
Item 11
This is item number 11in the list
Item 12
This is item number 12in the list
Item 13
This is item number 13in the list
Item 14
This is item number 14in the list
Item 15
This is item number 15in the list
Item 16
This is item number 16in the list
Item 17
This is item number 17in the list
Item 18
This is item number 18in the list
Item 19
This is item number 19in the list
Item 20
This is item number 20in the list
Item 21
This is item number 21in the list
Item 22
This is item number 22in the list
Item 23
This is item number 23in the list
Item 24
This is item number 24in the list
Item 25
This is item number 25in the list
Item 26
This is item number 26in the list
Item 27
This is item number 27in the list
Item 28
This is item number 28in the list
Item 29
This is item number 29in the list
Item 30
This is item number 30in the list
Item 31
This is item number 31in the list
Item 32
This is item number 32in the list
Item 33
This is item number 33in the list
Item 34
This is item number 34in the list
Item 35
This is item number 35in the list
Item 36
This is item number 36in the list
Item 37
This is item number 37in the list
Item 38
This is item number 38in the list
Item 39
This is item number 39in the list
Item 40
This is item number 40in the list
Item 41
This is item number 41in the list
Item 42
This is item number 42in the list
Item 43
This is item number 43in the list
Item 44
This is item number 44in the list
Item 45
This is item number 45in the list
Item 46
This is item number 46in the list
Item 47
This is item number 47in the list
Item 48
This is item number 48in the list
Item 49
This is item number 49in the list
Item 50
This is item number 50in the list
use leptos::prelude::*;
use crate::components::_coming_soon::drawer::{
Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerPosition, DrawerTitle, DrawerTrigger,
};
#[component]
pub fn DemoDrawerSideScrollable() -> impl IntoView {
view! {
<div class="text-center">
<h1 class="mb-4 text-4xl font-bold text-foreground">Vaul Drawer Demo</h1>
<p class="mb-8 text-muted-foreground">Click the button below to open the drawer</p>
<DrawerTrigger>Open Drawer</DrawerTrigger>
</div>
<Drawer>
<DrawerContent
position=DrawerPosition::Left
class="top-0 bottom-0 right-auto h-full max-h-full rounded-t-none w-[300px] rounded-r-[10px]"
>
<DrawerBody class="overflow-y-auto pr-4 h-full">
<DrawerTitle class="mt-4 text-base">Scrollable Side Drawer</DrawerTitle>
<DrawerDescription>
This drawer contains 50 scrollable items to demonstrate side scrolling behavior.
</DrawerDescription>
<div class="mt-4 space-y-2">
{(1..=50)
.map(|i| {
view! {
<div class="p-3 rounded-md border bg-muted border-border">
<p class="text-sm font-medium">Item {i}</p>
<p class="text-xs text-muted-foreground">This is item number {i}in the list</p>
</div>
}
})
.collect_view()}
</div>
</DrawerBody>
</DrawerContent>
</Drawer>
}
}