Side Panel
A panel beside the page for details, a form or anything else, full height.
const slot = useRef<HTMLDivElement>(null);
<div {...stylex.props(styles.row)}> <Page onSelect={setSelected} /> <SidePanelSlot ref={slot} /> <SidePanel onOpenChange={(open) => !open && setSelected(null)} open={selected !== null} > <SidePanelContent container={slot}> <SidePanelHeader> <SidePanelTitle>Signed in</SidePanelTitle> </SidePanelHeader> <SidePanelBody>…</SidePanelBody> <SidePanelFooter>…</SidePanelFooter> </SidePanelContent> </SidePanel></div>;Installation
$ npx shadcn@latest add @constella/side-panelUsage
import { SidePanel, SidePanelBody, SidePanelContent, SidePanelDescription, SidePanelFooter, SidePanelHeader, SidePanelSlot, SidePanelTitle,} from "@/components/primitives/side-panel";Put a SidePanelSlot after the whole page, header included, in a flex row, so the panel runs the full height of it. Pass its ref to SidePanelContent as container.
The panel opens there, so the content narrows beside it instead of being covered. It's a non-modal dialog: the content stays usable, and Escape or Close shuts it.
The header is one row, so a SidePanelDescription goes first in the body. It names the panel's description for screen readers, as a dialog's does.
On a narrow window, show the same details in a Sheet instead.
Composition
SidePanelSlotSidePanel└── SidePanelContent ├── SidePanelHeader │ └── SidePanelTitle ├── SidePanelBody │ └── SidePanelDescription └── SidePanelFooterAPI Reference
| Part | Adds |
|---|---|
SidePanel | Base UI Dialog, non-modal, not closed by an outside click |
SidePanelContent | container: the slot's ref; renders the portal and popup |
SidePanelHeader | A row sizes.header tall, pinned: the title, any actions, then Close |
SidePanelDescription | Muted text first in the body; the panel's accessible description |
SidePanelBody | Scrolls on its own |
SidePanelFooter | Pinned below the body |
Every part takes sx, applied last. For the rest, see Base UI Dialog.