Overlays
Sheet
A side panel that slides in from any edge of the viewport, built on react-aria-components and the Dialog surface. Use for filters, detail editing, and navigation drawers.
overlaysheetdrawerpanelside-panelmodal
Right (default)
A panel that slides in from the right edge — good for detail editing.
Bottom
A panel that slides up from the bottom edge — good for filters on mobile.
Left
A navigation drawer sliding in from the left edge.
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import type { DialogProps, ModalOverlayProps } from "react-aria-components"
import {
DialogTrigger as DialogTriggerPrimitive,
Modal,
ModalOverlay,
} from "react-aria-components"
import { cn } from "@/lib/utils"
import {
Dialog,
DialogBody,
DialogClose,
DialogCloseIcon,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/dialog"
/**
* Sheet — quebi design system
*
* A side panel that slides in from any edge of the viewport. Composes the
* Dialog surface inside a react-aria Modal/ModalOverlay. Use for filters,
* detail editing, navigation drawers, and other off-canvas content.
*
* Surface tokens: bg-quebi-bg, border-cyan-500/10. Depth via glow, not shadow.
*/
const Sheet = DialogTriggerPrimitive
interface SheetContentProps
extends Omit<ModalOverlayProps, "children">,
Pick<DialogProps, "aria-label" | "role" | "aria-labelledby" | "children"> {
closeButton?: boolean
isFloat?: boolean
side?: "top" | "bottom" | "left" | "right"
overlay?: Omit<ModalOverlayProps, "children">
}
const sideVariants: Record<string, string> = {
top: "entering:slide-in-from-top exiting:slide-out-to-top inset-x-0 top-0 rounded-b-quebi-md border-b data-[float=true]:inset-x-2 data-[float=true]:top-2 data-[float=true]:border-b-0",
bottom:
"entering:slide-in-from-bottom exiting:slide-out-to-bottom inset-x-0 bottom-0 rounded-t-quebi-md border-t data-[float=true]:inset-x-2 data-[float=true]:bottom-2 data-[float=true]:border-t-0",
left: "entering:slide-in-from-left exiting:slide-out-to-left-80 inset-y-0 left-0 h-auto w-3/4 overflow-y-auto border-r sm:max-w-80 data-[float=true]:inset-y-2 data-[float=true]:left-2 data-[float=true]:border-r-0",
right:
"entering:slide-in-from-right exiting:slide-out-to-right-80 inset-y-0 right-0 h-auto w-3/4 overflow-y-auto border-l sm:max-w-80 data-[float=true]:inset-y-2 data-[float=true]:right-2 data-[float=true]:border-r-0",
}
const SheetContent = ({
className,
isDismissable: isDismissableInternal,
side = "right",
role = "dialog",
closeButton = true,
isFloat = true,
overlay,
children,
...props
}: SheetContentProps) => {
const isDismissable = isDismissableInternal ?? role !== "alertdialog"
return (
<ModalOverlay
isDismissable={isDismissable}
className="entering:fade-in exiting:fade-out fixed start-0 top-0 z-50 size-full entering:animate-in exiting:animate-out overflow-hidden bg-black/60 backdrop-blur-sm entering:duration-500 exiting:duration-300"
{...props}
>
<Modal
data-float={isFloat}
className={cn(
// quebi DS — surface with bg-quebi-bg + cyan border, matching Dialog.
"fixed z-50 grid gap-4 border border-cyan-500/10 bg-quebi-bg text-white shadow-quebi-glow",
"transform-gpu transition ease-in-out will-change-transform [--visual-viewport-vertical-padding:16px]",
"data-[float=true]:rounded-quebi-md",
"entering:fade-in entering:animate-in entering:duration-500",
"exiting:fade-in exiting:animate-out exiting:duration-300",
sideVariants[side],
className,
)}
>
<Dialog className="sm:[--gutter:--spacing(6)]" aria-label={props["aria-label"]} role={role}>
{(values) => (
<>
{typeof children === "function" ? children(values) : children}
{closeButton && (
<DialogCloseIcon className="end-2.5 top-2.5" isDismissable={isDismissable} />
)}
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
)
}
const SheetTrigger = DialogTrigger
const SheetFooter = DialogFooter
const SheetHeader = DialogHeader
const SheetTitle = DialogTitle
const SheetDescription = DialogDescription
const SheetBody = DialogBody
const SheetClose = DialogClose
export type { SheetContentProps }
export {
Sheet,
SheetBody,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
}