Bottom sheet
A sheet that rests at a peek or full height and follows your finger.
pnpm dlx shadcn@latest add @uiarc/bottom-sheetLive · 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-sheetAdds 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.