Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Sheet for React

Slide a panel in from a screen edge for a form or a detail, and leave the page in sight. For React with Base UI, when a centered dialog would hide too much.

Framework
UI library

The panel is anchored to one edge of the screen, rather than floating over the center.

Use it when the task needs its own surface and the page underneath should stay in sight. A short confirmation fits better in a Dialog. In React, Base UI traps focus while the panel is open, and it can sit on any edge, including the bottom on a narrow screen.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Sheet anatomy: content, close, title, description, footer
SheetTrigger
SheetOverlay
SheetPositioner
SheetContent
SheetTitle
SheetDescription
SheetClose

Usage

import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
  Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter,
  SheetHeader, SheetTitle, SheetTrigger,
} from "@/components/ui/sheet";
const SheetDemo = () => (
  <Sheet>
    <SheetTrigger render={<Button variant="outline" />}>Open</SheetTrigger>
    <SheetContent>
      <SheetHeader>
        <SheetTitle>Edit profile</SheetTitle>
        <SheetDescription>
          Make changes to your profile here. Click save when you&apos;re done.
        </SheetDescription>
      </SheetHeader>
      <div className="grid flex-1 auto-rows-min gap-6 px-4">
        <Field>
          <FieldLabel htmlFor="sheet-demo-name">Name</FieldLabel>
          <Input
            className="min-w-0 max-w-full"
            defaultValue="Pedro Duarte"
            id="sheet-demo-name"
            type="text"
          />
        </Field>
        <Field>
          <FieldLabel htmlFor="sheet-demo-username">Username</FieldLabel>
          <Input
            className="min-w-0 max-w-full"
            defaultValue="@peduarte"
            id="sheet-demo-username"
            type="text"
          />
        </Field>
      </div>
      <SheetFooter>
        <Button type="submit">Save changes</Button>
        <SheetClose render={<Button variant="outline" />}>Close</SheetClose>
      </SheetFooter>
    </SheetContent>
  </Sheet>
);

Accessibility

Enter
Activate the focused control.
Space
Activate the focused control.

Demos

Side

side on SheetContent is top, right, bottom, or left, with right as the default. Top and bottom are capped at half the viewport here, so a long form scrolls inside the panel. Use the bottom edge on a phone, and a side edge for an inspector next to a list. The title, the focus trap, and Escape stay the same on every edge.

Preview width

No Close Button

showCloseButton set to false removes the corner icon. This panel then has a title and a description, and no footer button, so the written hint is to click outside. Hide that icon only when another close control is obvious, usually next to the primary action. Escape still dismisses a modal sheet, and the overlay click is a backup rather than the thing you want people to hunt for.

Preview width

Rtl

The trigger is Arabic, and the panel opens from the left with dir set to rtl on SheetContent. Title, fields, and footer actions align to the start of the line. Put dir on the content, not only the trigger, because the panel is portaled and can miss the page direction. side is still an explicit edge and does not flip on its own.

Preview width

FAQ

How do I control whether a Sheet is open in React?

The sheet starts closed and opens from its trigger. Control it when another part of the page must open or close it. Keep that boolean in state and pass it back, or use the default prop for the first state only. Pass open so onOpenChange receives the next boolean, and set defaultOpen when you only need the first state.

How do I change which edge a Sheet uses?

Set side on SheetContent to top, right, bottom, or left. The default is right, and bottom slides up from the bottom edge, which suits a narrow screen. The Side demo shows all four edges. The sheet still traps focus the same way on every edge.

Why does a Sheet need a SheetTitle?

SheetTitle names the dialog for assistive tech, so always render one. If the design has no visible heading, hide the title with the sr-only utility and keep it in the tree. SheetDescription explains the task and is optional. Escape closes the sheet and returns focus to the trigger.

How do I hide the Sheet close button?

The close button is shown by default. Set showCloseButton to false on SheetContent to hide it, as the No Close Button demo does. Still offer a SheetClose in the footer, or people may not see how to dismiss it. Escape still closes a modal sheet.

How do I use a button as the Sheet trigger with Base UI?

The trigger should look like your button, not like an extra wrapper. Put the visible label on the button so the control has an accessible name. Pass the button through the render prop on SheetTrigger, and do not use asChild. The button text stays the accessible name.

Should I use a Sheet, a Dialog, or a Drawer?

Use a Sheet when the panel should slide in from a screen edge and leave part of the page in view. Use a Dialog for a centered modal, such as a short confirmation. Use a Drawer when the panel should support swipe, usually from the bottom on a phone.

Why is my Sheet behind another layer, or clipping a long form?

Sheet content is portaled and stacked at z-50, so it sits above the page. If another overlay uses the same layer, edit the sheet file under components/ui and raise the overlay and content together. If a long form is clipped, let the body scroll, for example by wrapping it in a Scroll Area.

How do I install Sheet in React?

Run the command in the Installation section above. It adds Sheet and its parts to your ui folder. Import from that folder when the command finishes, instead of copying the source by hand, and rerun the command later if you want the latest files. The command uses the shadcn CLI.

API Reference

Sheet

data-slot="sheet"

PropType
open?boolean

Whether the dialog is currently open.

defaultOpen?boolean

Whether the dialog is initially open. To render a controlled dialog, use the open prop instead.

Default
false
onOpenChange?(open: boolean, eventDetails: DialogRoot.ChangeEventDetails) → void

Event handler called when the dialog is opened or closed.

Type
(open: boolean, eventDetails: DialogRoot.ChangeEventDetails) → void
Parameters

open – boolean

eventDetails – DialogRoot.ChangeEventDetails

Returns
void
actionsRef?React.RefObject<DialogRoot.Actions | null>

A ref to imperative actions. - unmount: Ends the closing phase of the dialog after an externally controlled closing animation finishes. Call preventUnmountOnClose() in onOpenChange first, otherwise the dialog completes closing on its own. Whether it leaves the DOM is decided by keepMounted on the portal. - close: Closes the dialog imperatively when called.

Type
React.RefObject<DialogRoot.Actions | null>
children?React.ReactNode | PayloadChildRenderFunction<Payload>

The content of the dialog. This can be a regular React node or a render function that receives the payload of the active trigger.

Type
React.ReactNode | PayloadChildRenderFunction<Payload>
defaultTriggerId?string | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the defaultOpen prop to create an initially open dialog.

Type
string | null
disablePointerDismissal?boolean

Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it.

Default
false
handle?DialogHandle<Payload>

A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method.

Type
DialogHandle<Payload>
modal?boolean | "trap-focus"

Determines if the dialog enters a modal state when open. - true: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - false: user interaction with the rest of the document is allowed. - trap-focus: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. When modal is true or trap-focus, render <Dialog.Close> inside <Dialog.Popup> so touch screen readers can escape the popup.

Type
boolean | "trap-focus"
Default
true
onOpenChangeComplete?(open: boolean) → void

Event handler called after any animations complete when the dialog is opened or closed.

Parameters

open – boolean

Returns
void
triggerId?string | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the open prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the open prop is not set).

Type
string | null
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)

SheetTrigger

<button> data-slot="sheet-trigger"

PropType
handle?DialogHandle<Payload>

A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method.

Type
DialogHandle<Payload>
id?string

ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot triggerId prop).

nativeButton?boolean

Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).

Default
true
payload?Payload

A payload to pass to the dialog when it is opened.

asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)

SheetOverlay

<div> data-slot="sheet-overlay"

PropType
forceRender?boolean

Whether the backdrop is forced to render even when nested.

Default
false
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"

SheetTitle

<h2> data-slot="sheet-title"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

SheetDescription

<p> data-slot="sheet-description"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

SheetClose

<button> data-slot="sheet-close"

PropType
nativeButton?boolean

Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).

Default
true
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)

SheetPortal

<div> data-slot="sheet-portal"

PropType
container?HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null

A parent element to render the portal element into.

Type
HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null
keepMounted?boolean

Whether to keep the portal mounted in the DOM while the popup is hidden.

Default
false
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)

SheetContent

data-slot="sheet-content"

PropType
showCloseButton?boolean

Show close button at the top right corner.

Default
true
side?"top" | "right" | "bottom" | "left"

Edge of the screen where the sheet appears.

Type
"top" | "right" | "bottom" | "left"
Default
"right"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-side?string

Mirrors side.

SheetHeader

<div> data-slot="sheet-header"

PropType
description?string

The description of the sheet.

title?string

The title of the sheet.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

SheetFooter

<div> data-slot="sheet-footer"

PropType
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

With other components

  • Free

    Button

    Buttons

    Trigger and footer actions

  • Free

    Field

    Inputs

    Fields inside the panel

  • Free

    Input

    Inputs

    Name, username

  • Free

    Label

    Inputs

    Visible or screen-reader only

  • Free

    Long lists inside

  • Free

    Separator

    Layout

    Split the panel