Bottom sheet

A sheet that rests at a peek or full height and follows your finger.

pnpm dlx shadcn@latest add @uiarc/bottom-sheet
Live · keyboard ready
Check-in openFri 24 Oct · TP 754
LISLisbon, 07:45
CPHCopenhagen, 12:20
  • Mobile-first secondary tasks such as details, filters, or share options.
  • Content that benefits from a peek height before expanding to nearly full screen.
  • Maps and media views where the sheet should be dragged between detents.
  • Use dialog for interrupting decisions on any screen size.
  • Use drawer for side panels on wide desktop layouts.
  • Use popover for small anchored content that should not dim the page.

Installation

Add Bottom sheet with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/bottom-sheet

Adds the component and its local dependencies, and installs @radix-ui/react-dialog, motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { BottomSheet, BottomSheetClose } from "@/registry/components/bottom-sheet/bottom-sheet";import { Button } from "@/registry/components/button/button"; export function TripSheet() {  return (    <BottomSheet      trigger={<Button>Trip details</Button>}      title="Lisbon, 3 nights"      description="Oct 12 to Oct 15"      detents={[0.4, 0.9]}    >      <p>Flights, hotel, and bookings.</p>      <BottomSheetClose asChild><Button variant="secondary">Done</Button></BottomSheetClose>    </BottomSheet>  );}

Controlled sheet with detent tracking

example.tsx
<BottomSheet  open={open}  onOpenChange={setOpen}  title="Filters"  detents={[0.5]}  onDetentChange={index => track("detent", index)}>  <FilterList /></BottomSheet>

API reference

2 parts. The first is the root.

BottomSheet

A modal sheet that rises from the bottom edge and rests at one or more detents, with drag, flick, and keyboard control.

PropTypeDefaultDescription
titleRequiredstring–Sheet title, rendered as Dialog.Title.
childrenRequiredReactNode–Scrollable sheet content.
triggerReactNode–Control that opens the sheet. Rendered with asChild; focus returns to it on close.
openboolean–Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => void–Called when the sheet opens or closes.
descriptionstring–Supporting line, rendered as Dialog.Description.
detentsnumber[][0.45, 0.92]Resting heights as fractions of the viewport height.
initialDetentnumber0Index into the sorted detents the sheet opens at.
onDetentChange(index: number) => void–Called when the sheet settles on a different detent.
closeLabelstring"Close"aria-label of the close button.
classNamestring–Class on the sheet surface.

BottomSheetClose

Radix Dialog.Close for buttons inside the sheet.

PropTypeDefaultDescription
...propsComponentPropsWithoutRef<typeof DialogPrimitive.Close>–Radix close props, including asChild.
ArrowUporArrowDown
On the grabber, moves one detent up or down.
HomeorEnd
On the grabber, jumps to the tallest or smallest detent.
EnterorSpace
On the grabber, toggles between smallest and tallest detent.
ArrowDownorPageDownorSpace
On the sheet, expands to full height first, then scrolls the content.
Escape
Closes the sheet.
  • Built on Radix Dialog: role="dialog", focus trap, and focus return to the trigger. Initial focus lands on the sheet itself.
  • The grabber is a button with aria-expanded and an Expand or Collapse sheet label.
  • Detent changes are announced through a polite live region.
  • Tabbing into content below the fold expands the sheet so focused elements are visible.
  • One motion value drives the sheet: drags follow the finger 1:1, rubber-band past the tallest detent, and releases project velocity to pick a detent or dismiss.
  • The backdrop dim is a function of sheet position, so it tracks drags instead of running on a timer.
  • Reduced motion jumps between detents and fades the sheet in and out.
  • The sheet is min(100%, 36rem) wide and detents are fractions of the dynamic viewport height, so it adapts to mobile browser chrome.
  • Below 40rem the header and body use tighter side padding.
  • Touch drags on the content move the sheet until it is fully open, then the content scrolls; mouse and pen drag from the header.
  • One motion value drives position and backdrop, so drags do not re-render React per frame.
  • A ResizeObserver re-fits detents on viewport changes; content is not virtualized, so paginate long lists inside it.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for mobile-first secondary tasks where a peek helps: details, filters, share options. Use dialog for interrupting decisions and drawer for side panels on wide layouts.
  • Pass a trigger or control open yourself. Use a single-value detents array for a fixed-height sheet.

The full library index for assistants is at /llms.txt.