ComponentsOverlays

Sheet

A modal panel that slides in from an edge of the screen. Use it for details, filters, and editors that need more room than a dialog but should keep the page behind in view. Focus stays inside until it closes. On a touch screen, swipe it back toward its edge to close it.

Usage

Sheet usageTSX
import { Button } from "@tulipfarm/sprout/button";
import {
  Sheet,
  SheetBody,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@tulipfarm/sprout/sheet";

<Sheet open={open} onOpenChange={setOpen}>
  <SheetTrigger render={<Button variant="outline" size="sm" />}>
    <Filter aria-hidden="true" />
    Filters (2)
  </SheetTrigger>
  <SheetContent side="bottom">
    <SheetHeader>
      <SheetTitle>Filter and sort</SheetTitle>
      <SheetDescription>Results update as you choose.</SheetDescription>
    </SheetHeader>
    <SheetBody>…</SheetBody>
    <SheetFooter>
      <Button onClick={() => setOpen(false)}>Show results</Button>
      <Button variant="ghost" size="sm" onClick={clear}>Clear filters</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

Sheet works controlled with open and onOpenChange, or uncontrolled with a SheetTrigger. To keep it open while a save runs or while a second sheet is on top, ignore the change in onOpenChange and hide the corner button with showCloseButton={!saving}.

Sides

side is "right" (default), "left","top", or "bottom". Top and bottom sheets span the width, grow with their content up to 85% of the height, and lay out footer actions in a row from 640px.

Sides

Each side as Cloud opens it. Escape, the overlay, or a close button dismisses the sheet; nothing is saved.

TopNotice · default · SheetClose in footer
RightCalendar pending work · default
BottomFiles filters · sm trigger with count, icon-sm compact trigger
LeftCalendar filters · lg

Sizes

size sets the width of left and right sheets: "default" (24rem), "lg" (32rem), or "xl" (42rem). "full" covers the viewport from any side and respects safe areas. SheetTitle size="lg" is the larger title for product-style detail sheets.

Sizes

default 24rem, lg 32rem, xl 42rem, and full screen. Below 640px side sheets take 75% of the width, and lg and xl take all of it.

defaultFollow workflows · title default
lgAgent builder preview · icon trigger · title default
xlIntegration detail · title lg · custom top bar
fullMobile canvas · no corner close · keepMounted

Swipe to close

Every sheet follows a touch swipe toward its side. A right sheet swipes right and a bottom sheet swipes down. Let go past half the panel, or flick it, and the sheet closes. A shorter swipe springs back. Scrolling a long body scrolls first and swipes only from the edge.

A mouse or pen drags the sheet only from the handle or empty panel space. Drags on text, fields, and buttons select and click as usual, so desktop forms are safe. showSwipeHandle adds a grab bar on the edge facing the page. It is off by default. Turn it on for bottom sheets on phones, where people expect it.

Bottom sheet with a handleTSX
<Sheet>
  <SheetTrigger render={<Button variant="outline" />}>Stock filters</SheetTrigger>
  <SheetContent side="bottom" showSwipeHandle>
    <SheetHeader>
      <SheetTitle>Stock filters</SheetTitle>
      <SheetDescription>Swipe down or press Show results to close.</SheetDescription>
    </SheetHeader>
    <SheetBody>…</SheetBody>
    <SheetFooter>
      <SheetClose render={<Button />}>Show results</SheetClose>
    </SheetFooter>
  </SheetContent>
</Sheet>

Swipe to close

Swipe toward the edge on a touch screen. A short swipe springs back.

Bottom with handleMobile filters · showSwipeHandle · swipe down
Right with handleNotes form · swipe right · mouse selects text

Cloud patterns

Put a custom bar and your own scroll region directly in SheetContent, or use SheetBody, which scrolls between the header and footer. initialFocus can target a title with tabIndex={-1}, finalFocus can return focus to a trigger ref, and keepMounted keeps drafts while the sheet is closed. A dialog or a second sheet can open on top.

Cloud patterns

Compositions from TulipFarm Cloud with sample data. Nothing leaves this page.

Workspace navigationSidebar on phones · left or right · custom width · finalFocus
Table optionsMenu item opens xl sheet
Import skillslg · controlled · inline error
GitHubxl · initialFocus on title · footer bar
Calendar panelsOne sheet · filters from the left, others from the right
Calendar eventlg · badges, details, link buttons
Create eventlg · keepMounted keeps the draft
Member profilelg · close button hidden while saving
Edit rowlg · sticky actions · confirm before leaving

States

Show a skeleton with role="status" while data loads, an errorStatePanel with a retry, and a short sentence when nothing needs attention.

States

Loading, error, empty, and a list with a nested editor. The status sheet stays open while the editor on top is open.

Member profile statusPick a result, then open

API

Sheet, SheetTrigger, SheetPortal,SheetClose, SheetOverlay, SheetTitle, and SheetDescription wrap the Base UI drawer parts and take their props, including render. SheetHeader, SheetBody, and SheetFooter render a div and accept render, so a body can be the form. Sheet leaves out swipeDirection and snap points. The side on SheetContent sets the swipe direction.

SheetContent takes side, size, showCloseButton, closeLabel, keepMounted, container, showSwipeHandle, and every Base UI popup prop such as initialFocus and finalFocus. It sets data-slot="sheet-content" (overridable), data-side, and data-size. Its corner button has data-slot="sheet-close", so you can move it with a className selector. SheetTitle takes size ("default" or "lg").

Accessibility

The panel has role="dialog" and is named by SheetTitle. Every sheet needs a title, even if it is hidden with sr-only. Tab and Shift+Tab stay inside, and Escape or a click on the overlay closes it unless you ignore the change.

Opening slides 2.5rem and fades over 180ms. Closing takes 120ms. A swipe moves the panel with the finger, and the overlay fades as it goes. Motion turns off when reduced motion is requested. The swipe still works. The handle is hidden from assistive tech, so keep a close button or a footer action in every sheet.

Theme tokens

Sheets use --sprout-overlay, --sprout-surface, --sprout-line, --sprout-text, --sprout-muted, --sprout-shadow-overlay, and --sprout-radius-panel. They need no brand color, so they look the same with the neutral and TulipFarm themes in light and dark mode.