Biji UI

Drawer

A panel that slides in from the edge of the screen, with focus trapping and scroll lock.

Installation

biji-ui = { version = "0.5.0", features = ["drawer"] }

Usage

use leptos::prelude::*;
use biji_ui::components::drawer;

#[component]
pub fn MyDrawer() -> impl IntoView {
    view! {
        <drawer::Root>
            <drawer::Trigger class="px-4 py-2 rounded-md bg-primary text-primary-foreground">
                "Open Drawer"
            </drawer::Trigger>
            // Full-screen overlay
            <drawer::Overlay
                class="fixed inset-0 z-[80] bg-black/50 transition"
                show_class="opacity-100 duration-300 ease-out"
                hide_class="opacity-0 duration-200 ease-in"
            />
            // Sliding panel
            <drawer::Content
                class="fixed inset-y-0 right-0 z-[90] w-80 bg-background border-l \
                       border-border shadow-xl transition-transform"
                show_class="translate-x-0 duration-300 ease-out"
                hide_class="translate-x-full duration-200 ease-in"
            >
                <div class="flex flex-col h-full p-6">
                    <drawer::Title class="text-lg font-semibold">
                        "Panel Title"
                    </drawer::Title>
                    <drawer::Description class="mt-1 text-sm text-muted-foreground">
                        "Supplementary description text."
                    </drawer::Description>
                    <div class="mt-auto">
                        <drawer::Close class="w-full px-4 py-2 rounded-md border">
                            "Close"
                        </drawer::Close>
                    </div>
                </div>
            </drawer::Content>
        </drawer::Root>
    }
}

RootWith

Use RootWith to access DrawerState inline via the let: binding. The state is Copy and safe to pass as a prop.

Drawer is closed

use leptos::prelude::*;
use biji_ui::components::drawer;

#[component]
pub fn MyDrawer() -> impl IntoView {
    view! {
        <drawer::RootWith let:d>
            <p class="mb-2 text-sm text-muted-foreground">
                {move || if d.open.get() { "Drawer is open" } else { "Drawer is closed" }}
            </p>
            <drawer::Trigger class="px-4 py-2 rounded-md bg-primary text-primary-foreground">
                "Open Drawer"
            </drawer::Trigger>
            <drawer::Overlay
                class="fixed inset-0 z-[80] bg-black/50 transition"
                show_class="opacity-100 duration-300 ease-out"
                hide_class="opacity-0 duration-200 ease-in"
            />
            <drawer::Content
                class="fixed inset-y-0 right-0 z-[90] w-80 bg-background border-l border-border shadow-xl transition-transform"
                show_class="translate-x-0 duration-300 ease-out"
                hide_class="translate-x-full duration-200 ease-in"
            >
                <div class="flex flex-col h-full p-6">
                    <drawer::Title class="text-lg font-semibold">"Settings"</drawer::Title>
                    <drawer::Close class="mt-auto w-full px-4 py-2 rounded-md border">
                        "Close"
                    </drawer::Close>
                </div>
            </drawer::Content>
        </drawer::RootWith>
    }
}

Examples

Side

Use the side prop on Root to control which edge the panel slides in from.

API Reference

Root / RootWith

NameTypeDefaultDescription
classString""CSS class applied to the wrapper `<div>`.
sideDrawerSideRightWhich edge the panel slides in from. Sets `data-side` on Content.
prevent_scrollbooltrueWhen true, prevents the page from scrolling while the drawer is open.
hide_delayDuration300msHow long to wait before unmounting Content after closing (match your CSS transition).
openboolfalseInitial open state.
on_open_changeOption<Callback<bool>>NoneCalled with `true` when opening and `false` when closing.

Trigger

NameTypeDefaultDescription
classString""CSS class applied to the `<button>` element.

Overlay

NameTypeDefaultDescription
classString""CSS class applied in both open and closed states.
show_classString""CSS class applied when the overlay is visible.
hide_classString""CSS class applied while the overlay is hiding.

Content

NameTypeDefaultDescription
classString""CSS class applied in both open and closed states.
show_classString""CSS class applied when the panel is open (e.g. `translate-x-0`).
hide_classString""CSS class applied while the panel is closing (e.g. `translate-x-full`).

Close

NameTypeDefaultDescription
classString""CSS class applied to the `<button>` element.

Title

NameTypeDefaultDescription
classString""CSS class applied to the `<h2>` element. Its `id` is auto-wired to `aria-labelledby` on Content.

Description

NameTypeDefaultDescription
classString""CSS class applied to the `<p>` element. Its `id` is auto-wired to `aria-describedby` on Content.

Data Attributes

AttributeDescription
data-state"open" when the drawer is visible; "closed" otherwise. Present on Trigger and Content.
data-side"top" | "right" | "bottom" | "left". Present on Content.

Keyboard Navigation

KeyDescription
TabMoves focus to the next focusable element inside the drawer panel.
Shift + TabMoves focus to the previous focusable element inside the drawer panel.
EscapeCloses the drawer and returns focus to the Trigger.