RadCN
Overlaysreadycomponentready

Drawer

A modal edge-panel primitive for bottom, side, and app-composed responsive Dialog or Drawer workflows.

Importimport { Drawer, DrawerContent, DrawerTrigger } from 'radcn/drawer'
Preview

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.

Preview
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.