Components · Sheet
Sheet
A side-anchored panel for settings, filters, details and navigation. It is built on the same Radix Dialog primitive as Dialog, so focus trapping, focus restore, Escape and overlay behaviour are the mature ones LUC already ships. A gesture drawer is deliberately out of scope: a Sheet is a panel, not a swipeable card.
Examples
import {
Sheet,
SheetBody,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from "@luc/ui";Edges
Four edges, one surface. Choose the edge by what the panel is for: right for settings and details, left for navigation, bottom for filters on small screens.
<Sheet open={edge !== null} onOpenChange={(next) => setEdge(next ? edge : null)}>
<Button
variant="secondary"
aria-haspopup="dialog"
onClick={() => openAt("right", size)}
>
right
</Button>
<SheetContent side="right" size="md">
<SheetHeader>
<SheetTitle>Chart settings</SheetTitle>
</SheetHeader>
<SheetBody>...</SheetBody>
<SheetFooter>
<Button>Apply</Button>
</SheetFooter>
</SheetContent>
</Sheet>Behaviour that comes for free
Inherited from the Dialog primitive, not re-invented here.
- Focus is trapped inside the panel and returned to the trigger on close.
- Escape closes; the overlay absorbs the click and the page behind never scrolls away.
- The panel is a labelled dialog, so screen readers announce it by its title.
- Under reduced motion the slide collapses to an immediate appearance.
// Sizes are a vocabulary, not a free-for-all: sm, md, lg, xl, full.
// Widths for left/right edges, heights for top/bottom.API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
Sheet | 5 documented props | ({ open, defaultOpen, onOpenChange, children, ...props }: SheetRootProps) => import("@types/react").JSX.Element |
SheetBody | export | ({ className, ...props }: React.ComponentPropsWithRef<"div">) => import("@types/react").JSX.Element |
SheetClose | 1 documented props | import("@types/react").ForwardRefExoticComponent<RadixDialog.DialogCloseProps & import("@types/react").RefAttributes<HTMLButtonElement>> |
SheetContent | 12 documented props | ({ className, children, side, size, showClose, onOpenAutoFocus, onCloseAutoFocus, ...props }: SheetContentProps) => import("@types/react").JSX.Element |
SheetDescription | 1 documented props | import("@types/react").ForwardRefExoticComponent<RadixDialog.DialogDescriptionProps & import("@types/react").RefAttributes<HTMLParagraphElement>> |
SheetFooter | export | ({ className, ...props }: React.ComponentPropsWithRef<"div">) => import("@types/react").JSX.Element |
SheetHeader | 1 documented props | ({ className, icon, children, ...props }: SheetHeaderProps) => import("@types/react").JSX.Element |
SheetRoot | 5 documented props | ({ open, defaultOpen, onOpenChange, children, ...props }: SheetRootProps) => import("@types/react").JSX.Element |
SheetTitle | 1 documented props | import("@types/react").ForwardRefExoticComponent<RadixDialog.DialogTitleProps & import("@types/react").RefAttributes<HTMLHeadingElement>> |
SheetTrigger | 1 documented props | import("@types/react").ForwardRefExoticComponent<RadixDialog.DialogTriggerProps & import("@types/react").RefAttributes<HTMLButtonElement>> |
Sheet props
Type: RadixDialog.DialogProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
children?inherited | ReactNode | — | |
defaultOpen?inherited | boolean | undefined | — | |
modal?inherited | boolean | undefined | — | |
onOpenChange?inherited | ((open: boolean) => void) | undefined | — | |
open?inherited | boolean | undefined | — |
SheetClose props
Type: DialogCloseProps & RefAttributes<HTMLButtonElement>
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — |
SheetContent props
Type: SheetContentProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
deferPointerDownOutside?inherited | boolean | undefined | — | When `true`, a `'pointerdown'` event outside of the layered element will wait for the interaction's click event before dispatching, allowing third-party code to stop propagation o… |
forceMount?inherited | true | undefined | — | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
onCloseAutoFocus?inherited | ((event: Event) => void) | undefined | — | Event handler called when auto-focusing on close. Can be prevented. |
onEscapeKeyDown?inherited | ((event: KeyboardEvent) => void) | undefined | — | Event handler called when the escape key is down. Can be prevented. |
onFocusOutside?inherited | ((event: FocusOutsideEvent) => void) | undefined | — | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
onInteractOutside?inherited | ((event: PointerDownOutsideEvent | FocusOutsideEvent) => void) | undefined | — | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown` event happens outside or focus moves outside of it. Can be prev… |
onOpenAutoFocus?inherited | ((event: Event) => void) | undefined | — | Event handler called when auto-focusing on open. Can be prevented. |
onPointerDownOutside?inherited | ((event: PointerDownOutsideEvent) => void) | undefined | — | Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
showClose? | boolean | undefined | — | Show the built-in close affordance. Default `true`. |
side? | SheetSide | undefined | — | Edge the panel is anchored to. Default `right`. |
size? | SheetSize | undefined | — | Width (vertical edges) or height (horizontal edges). Default `md`. |
SheetDescription props
Type: DialogDescriptionProps & RefAttributes<HTMLParagraphElement>
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — |
SheetHeader props
Type: SheetHeaderProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
icon? | ReactNode | — |
SheetRoot props
Type: RadixDialog.DialogProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
children?inherited | ReactNode | — | |
defaultOpen?inherited | boolean | undefined | — | |
modal?inherited | boolean | undefined | — | |
onOpenChange?inherited | ((open: boolean) => void) | undefined | — | |
open?inherited | boolean | undefined | — |
SheetTitle props
Type: DialogTitleProps & RefAttributes<HTMLHeadingElement>
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — |
SheetTrigger props
Type: DialogTriggerProps & RefAttributes<HTMLButtonElement>
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — |
Usage
import { SheetRoot, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetBody } from "@luc/ui/components/sheet";<SheetRoot>
<SheetTrigger asChild>
<Button>Filters</Button>
</SheetTrigger>
<SheetContent side="right" size="md">
<SheetHeader>
<SheetTitle>Filters</SheetTitle>
</SheetHeader>
<SheetBody>…</SheetBody>
</SheetContent>
</SheetRoot>Guidelines
Do
- Use `side="right"` for a settings or filter panel, `side="bottom"` for a mobile action sheet, and keep the body scrollable while the header stays put.
- Keep `SheetTitle` in every sheet; it is the panel's accessible name as well as its heading.
Don't
- Use a sheet for a short confirmation; `AlertDialog` says “this is a decision” in a way a panel does not.
Accessibility
The sheet is a modal dialog that enters from the side it will leave by. Focus moves into the panel, Tab stays inside it, Escape and the close control dismiss it, and focus returns to the trigger.
Related