Sheet

A panel that stays open over a full-bleed canvas like a map: a floating card on a wide container, a bottom sheet with three detents on a narrow one. Unlike Drawer, it never blocks the page.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add sheet

Usage#

Put the Sheet inside the positioned element it floats over, usually the box your map fills. It is absolute in there, and that element is what it measures: wider than breakpoint (768px by default) it floats as a card, narrower it rises from the bottom. It follows the container, not the viewport, so it behaves the same full-screen and inside a panel.

usage.tsxTSX
import {  Sheet, SheetHandle, SheetHeader, SheetTitle,  SheetDescription, SheetBody, SheetFooter,} from "@/components/ui/sheet"export function MapScreen() {  return (    <div className="relative h-dvh">      <Map className="absolute inset-0" />      <Sheet>        <SheetHandle />        <SheetHeader>          <SheetTitle>Nearby</SheetTitle>          <SheetDescription>Five places open now.</SheetDescription>        </SheetHeader>        <SheetBody>{/* … */}</SheetBody>        <SheetFooter>{/* … */}</SheetFooter>      </Sheet>    </div>  )}

Anatomy#

import {  Sheet,  SheetHandle,  SheetHeader,  SheetTitle,  SheetDescription,  SheetBody,  SheetFooter,} from "@/components/ui/sheet"<Sheet>  <SheetHandle />  <SheetHeader>    <SheetTitle />    <SheetDescription />  </SheetHeader>  <SheetBody>...</SheetBody>  <SheetFooter>...</SheetFooter></Sheet>

Detents#

On a narrow canvas the sheet rests at one of three heights: peek (the header and a row or two, peekHeight sets it), half, and full, which leaves a strip of the canvas visible at the top. Drag the handle or the header and let go: the release velocity is projected, so a flick carries it to the next detent. A tap on the handle steps it up. Control it with detent and onDetentChange, or let it start at defaultDetent.

Side and size#

The floating card docks to the left (the default) or right edge, top to bottom with a margin all round. size picks its width on the same steps Drawer uses (sm, md by default, lg, xl); a width class of your own replaces it. A bottom sheet always spans the canvas.

The visible area#

A map centred on the whole canvas puts half of what you look at under the sheet. onInsetChange reports how much of the canvas the sheet covers, per edge and in pixels, every time that changes (the layout, the detent, the width, and each frame of a drag). Hand it to the map as padding and it centres things in the part that is still visible.

Keyboard and screen readers#

The Sheet is a named <aside>, a complementary landmark, labelled by its SheetTitle (or pass an aria-label). Nothing traps focus and nothing behind it goes inert: it is part of the page. On a bottom sheet the handle is a focusable window splitter: ArrowUp and ArrowDown step between detents, Home and End jump to peek and full, Enter and Space step it up, and it announces the detent through aria-valuetext (rename them with detentLabels).

API reference#

Sheet

An absolute <aside> that measures its parent: at or above breakpoint it floats as a card, below it rises as a bottom sheet with three detents. Forwards all native <aside> props, and every part accepts className, merged last.

PropTypeDefaultDescription
detent"peek" | "half" | "full"-The bottom sheet's detent (controlled).
defaultDetent"peek" | "half" | "full""half"The detent it starts at when uncontrolled.
onDetentChange(detent: SheetDetent) => void-Called when a drag, tap, key or setDetent moves the detent.
side"left" | "right""left"The edge the floating card docks to.
size"sm" | "md" | "lg" | "xl""md"The floating card's width on the container scale; a bottom sheet always spans the canvas.
breakpointnumber768Canvas width (px) at and above which the sheet floats.
peekHeightnumber176Height (px) of the peek detent: the header and a row or two.
onInsetChange(insets: SheetInsets) => void-Reports how much of the canvas the sheet covers per edge (top, right, bottom, left, in px), on every layout, detent or width change and every drag frame.
density"compact" | "comfortable""compact"Padding and title size of the parts. Falls back to the nearest DensityProvider and passes it on to the content.
Data attributeDescription
data-layoutfloating or bottom; absent for the first frame, while the canvas is measured (the sheet stays invisible).
data-detentThe current detent, on a bottom sheet only.
data-draggingPresent while a finger or pointer is dragging the sheet.
data-sideleft or right.

SheetHandle

The bottom sheet's grab bar and keyboard control, a focusable role="separator": ArrowUp and ArrowDown step detents, Home and End jump to the ends, Enter, Space or a tap step it up. Renders nothing while the sheet floats; forwards all native <div> props.

PropTypeDefaultDescription
detentLabelsPartial<Record<SheetDetent, string>>{ peek: "Peek", half: "Half height", full: "Full height" }What the handle announces for each detent as its aria-valuetext, for your language.
aria-labelstring"Resize the sheet"The handle's accessible name.

SheetHeader

The title block. On a bottom sheet it is also a drag region, so the whole top can be grabbed. Forwards all native <div> props.

SheetTitle

An <h2>; the sheet is labelled by it unless the Sheet gets an aria-label or aria-labelledby. Forwards all native <h2> props.

SheetDescription

Muted supporting text under the title. Forwards all native <p> props.

SheetBody

The scrollable content between header and footer. Forwards all native <div> props.

SheetFooter

A row pinned to the sheet's bottom edge, the place for a tab bar or the primary action. A lone child fills it; give several flex-1 to share it. Forwards all native <div> props.

useSheet

A hook for anything inside the Sheet: returns { layout, detent, setDetent }, where layout is "floating", "bottom" or null before the first measure.

FAQ#

Use a Drawer for a task that interrupts: it opens over the page, dims it, traps focus and closes when you are done. Use a Sheet for a panel that lives next to the page for as long as the page is open, like the place list over a map or the layers of a design board. The Sheet never dims, traps or closes; the canvas under it stays fully usable.