/// Overlay
Tray
A bottom sheet: a panel that rises from the bottom edge for pickers, filters, and quick actions, with optional drag-to-dismiss.
import { Tray } from "@protocore/pds";Basics
Tray mirrors Sheet — same parts, Tray.Root / Trigger / Content / Title / Description / Footer / Close — but anchors to the bottom edge and spans the viewport width. size caps the height: sm 40vh, md 60vh (default), lg 85vh.
Drag to dismiss
dragToDismiss turns the grab handle into a drag target: pull the panel down past a third of its height to close it — the familiar mobile bottom-sheet gesture. The handle always renders (grabber) as an affordance; dragging is opt-in.
When to use it
A Tray is the bottom-edge counterpart to a Sheet. Where a Sheet slides in from the side for master-detail on wide screens, a Tray rises from the bottom — the natural home for touch-first pickers, filter panels, and quick action lists, especially on narrow viewports where a side drawer would feel cramped.
Choose a [Sheet](/overlay/sheet) for tall, scrollable detail views beside a list. Choose a [Dialog](/overlay/dialog) for a short, self-contained decision that reads better centered. Reach for a Tray when the content is a compact set of options or actions the user summons from the bottom.
Usage
Do
- Use it for pickers, filters, and quick actions — especially on touch.
- Pick a size that fits: sm for a short list, lg for a fuller panel.
- Include a Tray.Title for accessibility, as with Dialog and Sheet.
- Enable dragToDismiss for a mobile-native feel; the handle signals it.
Don't
- Use a Tray for long master-detail content — that's a Sheet.
- Stack multiple Trays; drive everything from one panel.
- Omit the Title — Radix warns and screen readers need the label.
- Make it taller than the content needs; a bottom sheet should feel light.
Accessibility
| Keys | Action |
|---|---|
| Esc | Closes the tray and returns focus to the trigger. |
| Tab | Cycles focus forward, trapped within the panel. |
| Shift + Tab | Cycles focus backward, trapped within the panel. |
| Enter / Space | Activates the focused control. |
- Built on Radix Dialog: role=dialog with aria-modal, focus trapped and restored on close.
- A Tray.Title is required to label the panel; Description describes it.
- The drag handle is decorative (aria-hidden); dismissal is always keyboard-reachable via Esc or the close button.
- Content outside the tray is hidden from assistive tech while it's open.
Tray.Content props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | |
className | string | — | |
dragToDismiss | boolean | false | Let the user drag the panel down past a threshold to dismiss it. |
grabber | boolean | true | Show the top grab handle. Enabled automatically when `dragToDismiss`. |
showClose | boolean | true | Render a top-right 32px ghost × close button. |
size | enum | md | Panel max-height. sm 40vh / md 60vh / lg 85vh. |
style | CSSProperties | — |