Search for a command to run...
Adaptive modal — a centered card on desktop, a drag-handle bottom sheet on mobile. Built on Silk-HQ. Default for production dialogs in `@delphi/features`; reach for `Dialog` only when the desktop shape doesn't need a mobile sheet counterpart.
DetachedSheet.Content automatically renders the dismiss affordance — a drag handle on mobile, an X close button on desktop (top-right). Pass showCloseButton={false} to suppress when the consumer wires its own dismiss. Width is handled by the primitive: the desktop card has a sensible minimum (md, 28rem) and grows to fit wider content; raise the floor with the size prop (sm/md/lg/xl). Mobile is always full-bleed. No inner fixed-width wrapper is needed.
DetachedSheet.RootState container. Wraps Silk-HQ's Sheet.Root and provides the Silk license + a dismiss handler over context (so CloseButton works without prop-drilling).
| Prop | Type | Default | Description |
|---|---|---|---|
presented | boolean | — | Controlled open state. |
onPresentedChange | (open: boolean) => void | — | Fires when the sheet opens or closes. |
When uncontrolled, manage state externally and pair with DetachedSheet.Trigger (asChild) for the open affordance.
DetachedSheet.ContentThe floating panel. Adaptive: centered card on min-width: 650px, bottom sheet below it. Forwards every Silk Sheet.Content prop.
| Prop | Type | Default | Description |
|---|---|---|---|
showCloseButton | boolean | true | Auto-renders the dismiss affordance — drag handle on mobile, X close button on desktop. Set false to render your own affordance. |
size | "sm" | "md" | "lg" | "xl" | "md" | Desktop min-width floor for the centered card (20 / 28 / 32 / 40rem). The card grows past it to fit wider content and is capped at the viewport. No effect on mobile (always full-bleed). |
On desktop the panel is w-fit with a size-driven min-width floor and a max-width: calc(100vw - 2rem) ceiling, so it is never narrower than the floor, never wider than the viewport, and grows to fit content in between. On mobile it is w-full. The default md (28rem) suits single-field forms; bump to lg/xl for multi-column content, lists, or recorders. You no longer need an inner fixed-width wrapper.
DetachedSheet.HeaderLayout slot. flex flex-col gap-1.5 p-6. Wraps Title + Description.
DetachedSheet.Title · DetachedSheet.DescriptionTypography slots. Title is required for screen-reader announcement (wrap in sr-only if you don't want it visible).
Title — text-xl font-semibold tracking-tight text-fg-high.Description — text-sm text-fg-mid.DetachedSheet.FooterLayout slot for action rows. flex flex-row gap-2 px-6 pt-4 pb-6.
DetachedSheet.CancelButton · DetachedSheet.SubmitButtonPre-tuned action buttons for the footer. CancelButton is variant="ghost", SubmitButton is variant="solid". Both ship h-12 flex-1 so they fill the footer evenly. Override with className if needed.
DetachedSheet.CloseButtonTop-right X button. Auto-included by Content on desktop — reach for it directly only when rendering a custom layout that opts out of the built-in affordance (showCloseButton={false}) but still wants the standard X.
DetachedSheet.HandleMobile-only drag handle. Auto-included by Content on mobile. Renders null on desktop. Use directly the same way as CloseButton if you've opted out of the built-in affordance.
DetachedSheet.Trigger · DetachedSheet.Outlet · DetachedSheet.Backdrop · DetachedSheet.View · DetachedSheet.PortalPass-throughs / structural wrappers around Silk's primitives. Trigger accepts asChild like Radix triggers.
Use DetachedSheet for production dialogs in features — create / edit forms, info panels,
confirmations that need to look great on both mobile and desktop with a single component.
Let the primitive own width. The default md card is usable out of the box; reach for the
size prop (lg/xl) when the content needs a wider floor. Don't put width utilities on
DetachedSheet.Content — it's w-fit, so a max-w-*/w-* className there won't widen the
visible card.
Use DetachedSheet.CancelButton + DetachedSheet.SubmitButton inside DetachedSheet.Footer
for action rows. They're sized for both desktop and mobile commit surfaces (h-12 flex-1).
Leave showCloseButton on its default true. The primitive picks the right affordance per
viewport — drag handle on mobile, X on desktop.
Wire your own close button when showCloseButton is on. You'll double up the dismiss affordance
on desktop and have an extra element to maintain.
Reach for Dialog when the screen also needs to work on mobile. Dialog is desktop-centric;
DetachedSheet is the adaptive default.
Skip DetachedSheet.Title because the visible header has a different heading. Accessibility
relies on the title being present; hide it with sr-only if the visual design omits it.