Sophic Interface Systems
Components

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

modeBehavior
overlayPortaled Sheet over the page (inset card frame)
push-reservePage width shrinks; flush full-height rail beside content
push-scalePage scales toward center; flush rail slides in
push-translatePage 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):

ValueIncoming paneCovered layer
slideSlides from the rightShrinks toward center + light shell dim; bounces back on pop
zoom-slideSlides from the right while scaling up from smallSame covered treatment as slide
shiftSlides from the rightShifts left + top-left scale + light shell dim
shift-fadeOpaque slide from the right (same shift as shift); fill clears to transparent after settle so shell frost showsSame left + scale + shell dim wash as shift, then under-layer fades out late; dim clears when the top frosts
settleScales/fades in placeShrinks 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 top
  • pop() — remove the top pane
  • depth — 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 overlay is 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 — DrawerStack shift-fade animation: 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 (not transform); 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) via DrawerLayout; 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

On this page