Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Primitives
  2. Drawer
Primitives

Drawer

Swipeable edge panel for mobile sheets and off-canvas surfaces.

Composition

The preferred shape for composing the installed primitive from its exported parts.

Parts

Exported compound parts from the installed source.

Drawer
├── DrawerTrigger
├── DrawerClose
├── DrawerContent
├── DrawerHeader
├── DrawerFooter
├── DrawerTitle
└── DrawerDescription

The refined source installs this primitive from src/registry/sources/refined/ui/drawer.tsx. Install it on its own with the command above, or inspect the source below.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/drawer.json
See registry manifest

Raw code

Primary installed primitive source

Base UI Drawer slot
src/registry/sources/refined/ui/drawer.tsx
"use client";
​
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
import type { ComponentProps } from "react";
import { cn } from "@/components/refined-ui/lib/cn";
import { skinEffects, skinId, skinSlot } from "@/components/refined-ui/skin";
​
// Refined skin slot. 100% Base UI Drawer — native swipe gestures/snap points, no vaul.
// Panel tracks pointer via --drawer-swipe-movement-* vars; tempo from theme.
// Panel+backdrop re-assert skin scope on portal, like Sheet/Dialog.
​
type DrawerSide = "bottom" | "top" | "right" | "left";
​
// swipeDirection = drag axis that dismisses; same edge it's pinned to.
const swipeFor: Record<DrawerSide, ComponentProps<typeof DrawerPrimitive.Root>["swipeDirection"]> = {
bottom: "down",
top: "up",
right: "right",
left: "left",
};
​
// Per-edge viewport placement, popup shape, closed-state slide transform.
// Literal class strings — Tailwind's scanner can't see interpolated classes.
const placement: Record<DrawerSide, { viewport: string; popup: string }> = {
bottom: {
viewport: "items-end justify-center",
popup: "w-full max-h-[85vh] rounded-t-panel border-t data-[starting-style]:translate-y-full data-[ending-style]:translate-y-full",
},
top: {
viewport: "items-start justify-center",
popup: "w-full max-h-[85vh] rounded-b-panel border-b data-[starting-style]:-translate-y-full data-[ending-style]:-translate-y-full",
},
right: {
viewport: "items-stretch justify-end",
popup: "h-full w-3/4 max-w-sm border-l data-[starting-style]:translate-x-full data-[ending-style]:translate-x-full",
},
left: {
viewport: "items-stretch justify-start",
popup: "h-full w-3/4 max-w-sm border-r data-[starting-style]:-translate-x-full data-[ending-style]:-translate-x-full",
},
};
​
export function Drawer(props: ComponentProps<typeof DrawerPrimitive.Root> & { side?: DrawerSide }) {
const { side = "bottom", swipeDirection, ...rest } = props;
return <DrawerPrimitive.Root swipeDirection={swipeDirection ?? swipeFor[side]} {...rest} />;
}
​
export function DrawerTrigger({ className, ...props }: ComponentProps<typeof DrawerPrimitive.Trigger>) {
return (
<DrawerPrimitive.Trigger
data-ui="agent"
data-slot="drawer-trigger"
className={cn(skinSlot("drawer-trigger", {}), className)}
{...props}
/>
);
}
​
export function DrawerClose({ className, ...props }: ComponentProps<typeof DrawerPrimitive.Close>) {
return (
<DrawerPrimitive.Close data-ui="agent" data-slot="drawer-close" className={cn(skinSlot("drawer-close", {}), className)} {...props} />
);
}
​
export function DrawerContent({
className,
children,
side = "bottom",
...props
}: ComponentProps<typeof DrawerPrimitive.Popup> & { side?: DrawerSide }) {
const place = placement[side];
const grabbable = side === "bottom" || side === "top";
return (
<DrawerPrimitive.Portal>
{/* Portal escapes token-scoped ancestor — backdrop+popup re-assert skin scope. */}
<DrawerPrimitive.Backdrop
data-skin={skinId()}
data-effects={skinEffects()}
className="fixed inset-0 z-[70] bg-[oklch(from_var(--foreground)_l_c_h/var(--overlay-opacity))] backdrop-blur-[var(--overlay-blur)] transition-opacity duration-[var(--duration-base)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:bg-[oklch(from_var(--background)_l_c_h/var(--overlay-opacity))]"
/>
<DrawerPrimitive.Viewport
data-skin={skinId()}
data-effects={skinEffects()}
className={cn("fixed inset-0 z-[71] flex", place.viewport)}
>
<DrawerPrimitive.Popup
data-ui="agent"
data-slot="drawer-content"
className={cn(
"flex flex-col gap-4 py-4 bg-background text-foreground shadow-modal outline-none [transform:translate(var(--drawer-swipe-movement-x,0),var(--drawer-swipe-movement-y,0))] transition-transform duration-[var(--duration-base)] ease-[var(--ease-emphasized)] data-[swiping]:duration-0",
place.popup,
skinSlot("drawer-content", {}),
className,
)}
{...props}
>
{grabbable ? (
<div
data-ui="agent"
data-slot="drawer-handle"
className={cn(
"mx-auto h-1.5 w-12 shrink-0 rounded-full bg-muted-foreground/30",
side === "top" && "order-last",
skinSlot("drawer-handle", {}),
)}
/>
) : null}
<DrawerPrimitive.Content className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto">{children}</DrawerPrimitive.Content>
</DrawerPrimitive.Popup>
</DrawerPrimitive.Viewport>
</DrawerPrimitive.Portal>
);
}
​
export function DrawerHeader({ className, ...props }: ComponentProps<"div">) {
return (
<div
data-ui="agent"
data-slot="drawer-header"
className={cn("flex flex-col gap-1.5 px-4 text-center sm:text-left", skinSlot("drawer-header", {}), className)}
{...props}
/>
);
}
​
export function DrawerFooter({ className, ...props }: ComponentProps<"div">) {
return (
<div
data-ui="agent"
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 px-4", skinSlot("drawer-footer", {}), className)}
{...props}
/>
);
}
​
export function DrawerTitle({ className, ...props }: ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-ui="agent"
data-slot="drawer-title"
className={cn("font-semibold text-foreground", skinSlot("drawer-title", {}), className)}
{...props}
/>
);
}
​
export function DrawerDescription({ className, ...props }: ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-ui="agent"
data-slot="drawer-description"
className={cn("text-sm text-muted-foreground", skinSlot("drawer-description", {}), className)}
{...props}
/>
);
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Source