Drawer
An edge-anchored dialog that slides in from any side — settings panels, carts, mobile nav.
"use client";
import { Drawer, Button, Text, VStack } from "astralis-ui";
export function DrawerDemo() {
return (
<Drawer>
<Drawer.Trigger>
<Button variant="outline" colorScheme="gray">Open settings</Button>
</Drawer.Trigger>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Workspace settings</Drawer.Title>
<Drawer.CloseButton />
</Drawer.Header>
<Drawer.Body>
<VStack gap="3" alignItems="stretch">
<Drawer.Description>
Slides in from the right by default; the body scrolls
independently of the header and footer.
</Drawer.Description>
<Text size="sm" color="muted">Notifications, members, billing…</Text>
</VStack>
</Drawer.Body>
<Drawer.Footer>
<Drawer.Close>
<Button variant="text" colorScheme="gray">Cancel</Button>
</Drawer.Close>
<Drawer.Close>
<Button>Save</Button>
</Drawer.Close>
</Drawer.Footer>
</Drawer.Content>
</Drawer>
);
}Import#
import { Drawer } from "astralis-ui";
// Same anatomy as Modal: Drawer.Trigger, .Content, .Header,
// .Body, .Footer, .Title, .Description, .Close, .CloseButtonUsage#
Drawer shares Modal's anatomy and behavior — focus trap, scroll lock, Escape/backdrop dismissal — but anchors to an edge and slides instead of fading in the center. If the content is a critical decision, use Modal; if it's a workspace panel, use Drawer.
Placements#
placement picks the edge. For left/right the size prop sets width
(18–32rem, or full); for top/bottom it sets height (30–80vh, or full).
"use client";
import { Drawer, Button, HStack } from "astralis-ui";
const placements = ["left", "right", "top", "bottom"] as const;
export function DrawerPlacements() {
return (
<HStack gap="3" wrap="wrap" justifyContent="center">
{placements.map((placement) => (
<Drawer key={placement} placement={placement} size="sm">
<Drawer.Trigger>
<Button variant="subtle" size="sm">{placement}</Button>
</Drawer.Trigger>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>From the {placement}</Drawer.Title>
<Drawer.CloseButton />
</Drawer.Header>
<Drawer.Body>
<Drawer.Description>
left/right size the width; top/bottom size the height.
</Drawer.Description>
</Drawer.Body>
</Drawer.Content>
</Drawer>
))}
</HStack>
);
}Props#
| Prop | Type | Default | Description |
|---|---|---|---|
open / defaultOpen / onOpenChange | boolean / boolean / (open) => void | — / false / — | Controlled / initial state and callback. |
placement | "left" | "right" | "top" | "bottom" | "right" | Edge to slide from. |
size | "sm" | "md" | "lg" | "xl" | "full" | "md" | Width (left/right) or height (top/bottom). |
closeOnOverlayClick | boolean | true | Dismiss on backdrop click. |
closeOnEsc | boolean | true | Dismiss on Escape. |
The parts (Trigger, Content, Header, Body, Footer, Title,
Description, Close, CloseButton) behave exactly as documented on
Modal.
Keyboard#
| Key | Action |
|---|---|
Esc | Close (opt out with closeOnEsc={false}) |
Tab / Shift+Tab | Cycle focus inside the drawer — it can't escape |
Focus returns to the trigger on close; with stacked overlays, Escape peels the topmost layer first.
Accessibility#
Identical to Modal: role="dialog" + aria-modal, portalled, focus-trapped,
scroll-locked, with Title/Description wired to the dialog automatically.