Drawer
A modal edge-panel primitive for bottom, side, and app-composed responsive Dialog or Drawer workflows.
import { Drawer, DrawerContent, DrawerTrigger } from 'radcn/drawer'Live package example
Render the upstream Move Goal drawer and responsive edit-profile Dialog/Drawer examples with app-owned state, chart, form, and viewport branching.
Demo and Responsive Dialog
Render the upstream Move Goal drawer and responsive edit-profile Dialog/Drawer examples with app-owned state, chart, form, and viewport branching.
Move Goal
Set your daily activity goal.
Edit profile
Make changes to your profile here. Click save when you're done.
Edit profile
Make changes to your profile here. Click save when you're done.
import { Button } from 'radcn/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} from 'radcn/dialog'
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerTitle,
DrawerTrigger,
} from 'radcn/drawer'
import { Input } from 'radcn/input'
import { Label } from 'radcn/label'
export function DrawerPreview() {
return (
<>
<Drawer defaultOpen direction="bottom" id="move-goal">
<DrawerTrigger class="radcn-button radcn-button--outline">
Open Drawer
</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent direction="bottom" showHandle>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div style="margin:0 auto;width:100%;max-width:24rem;">
<Button ariaLabel="Decrease" size="icon" variant="outline">-</Button>
<strong>350</strong>
<Button ariaLabel="Increase" size="icon" variant="outline">+</Button>
<span>Calories/day</span>
</div>
<DrawerFooter>
<Button type="submit">Submit</Button>
<DrawerClose class="radcn-button radcn-button--outline">Cancel</DrawerClose>
</DrawerFooter>
</DrawerContent>
</DrawerPortal>
</Drawer>
<Dialog id="edit-profile-desktop">
<DialogTrigger class="radcn-button radcn-button--outline">Edit Profile</DialogTrigger>
<DialogPortal>
<DialogOverlay />
<DialogContent style="width:min(100%,425px);">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<Label for="email">Email</Label>
<Input id="email" type="email" value="shadcn@example.com" />
<Label for="username">Username</Label>
<Input id="username" value="@shadcn" />
<DialogFooter><Button type="submit">Save changes</Button></DialogFooter>
</DialogContent>
</DialogPortal>
</Dialog>
<Drawer direction="bottom" id="edit-profile-mobile">
<DrawerTrigger class="radcn-button radcn-button--outline">Edit Profile</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent direction="bottom" showHandle>
<DrawerHeader style="text-align:left;">
<DrawerTitle>Edit profile</DrawerTitle>
<DrawerDescription>
Make changes to your profile here. Click save when you're done.
</DrawerDescription>
</DrawerHeader>
<form method="post" style="display:grid;gap:1rem;padding:0 1rem;">
<Label for="mobile-email">Email</Label>
<Input id="mobile-email" type="email" value="shadcn@example.com" />
<Label for="mobile-username">Username</Label>
<Input id="mobile-username" value="@shadcn" />
<Button type="submit">Save changes</Button>
</form>
<DrawerFooter>
<DrawerClose class="radcn-button radcn-button--outline">Cancel</DrawerClose>
</DrawerFooter>
</DrawerContent>
</DrawerPortal>
</Drawer>
</>
)
}Installation
Intended future install command. RadCN is private and not published to npm yet, so this snippet documents the target user-facing API rather than something external consumers can run today.
pnpm add radcn # intended future package
import { Drawer, DrawerContent, DrawerTrigger } from 'radcn/drawer'Theming
RadCN tokens read the resolved theme from the document. Store the user's preference separately, then resolve system preferences to a concrete light or dark theme before setting package tokens.
<html data-radcn-theme-mode="system" data-radcn-theme="dark">
...
</html>Accessibility
- Enhanced drawers assign dialog roles, aria-labelledby, aria-describedby, focus movement, Escape handling, outside dismissal, focus restoration, and scroll locking.
- DrawerTrigger and DrawerClose are native buttons and expose aria-expanded, aria-controls, and visible or labelled text.
- Move Goal icon-style controls use ariaLabel for Decrease and Increase while the glyphs remain presentation.
- The edit-profile example composes real Label and Input controls so the form remains accessible independent of the Drawer primitive.
Customization
- Drawer exposes root, trigger, portal, overlay, content, handle, header, footer, title, description, and close hooks with classes, data attributes, style, and CSS variables.
- Use class and style on DrawerContent for shadcn-style max-width, padding, alignment, and bottom-sheet surface tuning.
- Button, Input, Label, Dialog, native forms, chart visualization, and responsive branch markup remain ordinary composition around the Drawer package.
- Min/max goal states, disabled controls, increments, chart data, and viewport branch selection are app-owned state that can be enhanced without changing radcn/drawer.
Remix 3 Notes
- React props/state, Vaul DrawerPrimitive, controlled open/onOpenChange, useState, useMediaQuery, asChild, className, data-slot, cn, and Tailwind utilities map to explicit RadCN props, browser enhancement, class, public data-radcn hooks, package CSS, and app-owned state.
- Button asChild maps to styling DrawerTrigger and DrawerClose directly with Button classes or composing RadCN Button where a nested button is not involved.
- Minus, Plus, lucide-react, Recharts, chart engines, form-state libraries, and media-query hooks are app presentation or app state and are not RadCN dependencies.
- The docs use dependency-free chart bars as the Recharts composition substitute while preserving the user-facing Move Goal chart slot.
- The responsive Dialog/Drawer example is proven with deterministic desktop and mobile branch fixtures; applications own the actual breakpoint policy.
- Vendor source remains read-only evidence and is not imported by RadCN.
