Drawer
Edge panel for inspection — overlay or push that reshapes the page.
Product name is Drawer. Use it for edge inspection panels such as holding
detail. Overlay mode is a Sheet under the hood; push modes live in
@sophic/portal-ui and shrink or shift the page as the rail slides in.
Responsive overlay: below Tailwind lg (1024px) the overlay slides
bottom → top as an inset card that stops ~10% from the top, with a
subtle dimming wash behind the sheet; at lg+ it stays the right 450px
rail (undimmed unless overlay is set). See
Responsive Behaviour.
You might be looking for:
- Dialog
A modal for focused tasks without leaving the page.
- Confirm Dialog
Yes/no with Cancel and Confirm — no close button.
Modes
mode | Behavior |
|---|---|
overlay | Portaled Sheet over the page (inset card frame) |
push-reserve | Page width shrinks; flush full-height rail beside content |
push-scale | Page scales toward center; flush rail slides in |
push-translate | Page translates left; flush rail occupies the strip |
Push modes need a wrapping DrawerLayout. Below sm, push falls back to a
fullscreen Sheet. Click the page to dismiss (no dim); Esc and close also work.
Wheel / touch over the drawer panel stay there (including empty frame) so they
do not scroll the page behind; the page stays interactive outside the panel.
Playground
<DrawerLayout> <Page /> <Drawer mode="push-reserve" open={open} onOpenChange={setOpen}> <Details /> </Drawer></DrawerLayout>Usage
import {
Drawer,
DrawerClose,
DrawerDescription,
DrawerLayout,
DrawerTitle,
} from "@sophic/portal-ui";
<DrawerLayout>
<Page />
<Drawer
mode="push-reserve"
open={open}
onOpenChange={setOpen}
width="450px"
>
<DrawerTitle>Details</DrawerTitle>
<DrawerDescription className="sr-only">Overview</DrawerDescription>
{/* body */}
<DrawerClose asChild>
<Button variant="outline">Close</Button>
</DrawerClose>
</Drawer>
</DrawerLayout>;Route-backed drawers (Portal)
In the Portal app, holding / order / activity detail is driven by query
params on any private route — for example /home?holding=<id>,
/activity?activity=<id>, /instruments/xyz?order=<id>. The current top
entity is the only param; browser history is the stack (open another entity
while the drawer is open → push; Back → pop; empty → slide closed). X /
outside / Esc pops every drawer open with history.go(-n) onto the page
underneath (no closed-URL push). Forward after that dismiss may still walk
the old drawer URLs — the History API cannot delete them without a duplicate
entry. Deep links seed a closed entry under the open URL once on hard
land (not on Back/Forward reopen) so Back closes onto the current page
(instrument / activity / home; fallback /home). Inner holding drill-ins
stay in-memory (DrawerStack) for now.
Openers should mark themselves with data-drawer-target so a click does not
count as dismiss-outside while the rail is open (SlickDataTable rows and
ItemList items do this automatically when interactive).
Overlay-only (no layout host) still works with mode="overlay", or the
underlying Sheet import:
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@sophic/portal-ui/components/ui/sheet";<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open drawer</Button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Details</SheetTitle>
</SheetHeader>
{/* body */}
</SheetContent>
</Sheet>Drawer props
Prop
Type
Default
DrawerLayout props
Prop
Type
Default
DrawerStack
When the same open Drawer needs nested navigation (push a pane, pop on back)
without closing the shell, wrap the body in DrawerStack from
@sophic/portal-ui. The root pane stays mounted; drill-ins are pushed when
the user navigates.
animation controls push/pop motion (default slide):
| Value | Incoming pane | Covered layer |
|---|---|---|
slide | Slides from the right | Shrinks toward center + light shell dim; bounces back on pop |
zoom-slide | Slides from the right while scaling up from small | Same covered treatment as slide |
shift | Slides from the right | Shifts left + top-left scale + light shell dim |
shift-fade | Opaque slide from the right (same shift as shift); fill clears to transparent after settle so shell frost shows | Same left + scale + shell dim wash as shift, then under-layer fades out late; dim clears when the top frosts |
settle | Scales/fades in place | Shrinks toward center + light shell dim |
Stack playground
<DrawerStack> <DetailsRoot /></DrawerStack>import { DrawerStack, useDrawerStack } from "@sophic/portal-ui";
function DetailsRoot() {
const { push, pop } = useDrawerStack();
return (
<button
type="button"
onClick={() =>
push(<DrillIn onBack={pop} />, { key: "drill-in" })
}
>
Open drill-in
</button>
);
}
<Drawer mode="overlay" open={open} onOpenChange={setOpen}>
<DrawerStack animation="slide">
<DetailsRoot />
</DrawerStack>
</Drawer>;Only declare the root up front. Remounting DrawerStack (e.g.
key={holdingId}) clears history.
DrawerStack props
Prop
Type
Default
useDrawerStack
Returns { push, pop, depth } from the nearest DrawerStack.
push(node, { key? })— overlay a pane over the current toppop()— remove the top panedepth— number of pushed panes (0 = root only)
SheetContent props
Shell content props when using Sheet directly (code name SheetContent):
Prop
Type
Default
Changelog
- 8 September 2026 — Overlay and rail corners use 16px (
--radius-2xl) on product tokens. Full notes - 24 August 2026 — Overlay and push-rail chrome in a CSS module; look unchanged. Full notes
- 19 August 2026 — “You might be looking for” map to Dialog. Full notes
- 12 August 2026 — Detail sheets keep their content through the overlay slide-out; stack session remounts on the next open. Full notes
- 11 August 2026 — Bottom overlay sheets stop ~10% from the top and
always show a subtle page wash; desktop right rails stay undimmed unless
overlayis set. Full notes - 7 August 2026 — Portal product defaults: Drawer Mode overlay,
Drawer Animation shift-fade, page-transition motion off. X /
outside / Esc dismiss pops the drawer stack with
history.go(-n)(no History experiment). - 6 August 2026 —
DrawerStackshift-fadeanimation: shift enter with opaque fill that clears to shell frost after settle; covered layer washes then fades out late. - 6 August 2026 — Portal route-backed drawers via
?holding=/?order=/?activity=on any private path; history encodes the entity stack. - 5 August 2026 — Push rail slides via
right(nottransform); frost paints on the host (not::before) so Glass 2.0 wash + blur both show. Full notes - 28 July 2026 — Push modes (
push-reserve,push-scale,push-translate) viaDrawerLayout; overlay remains the Sheet path. Full notes - 28 July 2026 — DrawerStack covered-pane wash on stacked panes. Full notes
- 28 July 2026 — Drawer docs + DrawerStack animation variants. Full notes