Sheet

A workspace-level bottom sheet built on Base UI Drawer, with App’s floating sheet geometry, software-keyboard handling, and a claim on the one shared dim.

Installation

import { Sheet } from "@o/pipeline/experimental/components/sheet";

Usage

Sheet is for a surface that takes the whole screen in its own right, the command palette being the first. It is not App.Drawer, which is the sidebar stack and is deliberately scoped to it. A sheet portals to the document, sits on the --z-sheet layer between the drawer stack and dialogs, and dims whatever it covers.

<Sheet.Root open={open} onOpenChange={setOpen}>
  <Sheet.Portal>
    <Sheet.Backdrop />
    <Sheet.Viewport>
      <Sheet.Popup aria-label="Command palette">
        <Sheet.Content>{children}</Sheet.Content>
        <Sheet.Close>Close</Sheet.Close>
      </Sheet.Popup>
    </Sheet.Viewport>
  </Sheet.Portal>
</Sheet.Root>
  • Root is modal and swipes down by default, and mounts Base UI’s VirtualKeyboardProvider for you, because the popup’s geometry is only correct while something is publishing --drawer-keyboard-inset.
  • Portal renders into document.body unless you name a container, the same default Dialog.Portal takes. Without it floating-ui nests the sheet into whichever App panel it was mounted under, and the sheet cannot escape that panel’s stacking context.
  • Backdrop force-renders Base UI’s part (which skips itself whenever a drawer looks nested, and every drawer inside App.Root does) and claims the shared dim at the sheet layer. The part itself is transparent, and is the press and swipe target.
  • Popup is content-sized up to 100dvh less App’s mobile insets, and accounts for the software keyboard once, by raising its own bottom edge and subtracting the same term from the cap. Do not add keyboard padding inside it.
  • Close is Base UI’s close part. Where the control goes is the consumer’s call: CommandMenu.Dialog places one at the top end of the sheet for touch, since there is no Escape key there.

There are no snap points yet.