SlidePanel is a portaled, controlled panel that animates in from the left or right edge of the viewport. It sits next to the page rather than on top of it: the rest of the UI stays visible behind a soft backdrop blur, and the panel body lives inside a rounded, bordered surface that is inset from the viewport by 12px on three sides. Reach for it when the user needs to create, edit, or inspect something without losing their place in the list or view they came from.
Where a Drawer covers the full edge of the screen and a Drover takes over
the viewport entirely, SlidePanel is deliberately lighter. It shares the
page with the content that launched it, so filter selections, table rows, or
surrounding state remain readable through the translucent backdrop. The
compound API is fully controlled via isOpen and onClose, closes on
Escape and backdrop click, and guards its subtree with inert while
closed so tabbing and screen-reader focus don’t leak in.
Import
import { SlidePanel } from "@unkey/ui";
All subcomponents are accessed as properties of SlidePanel:
SlidePanel.Root, SlidePanel.Header, SlidePanel.Content,
SlidePanel.Footer, and SlidePanel.Close.
Basic
Drive the panel from a useState boolean. SlidePanel.Root renders the
backdrop and the sliding surface; compose it with a header, a content
region, and an optional footer. SlidePanel.Close wires itself to the
root’s onClose handler via context, so any button inside the panel can
dismiss it without prop drilling.
Side
Pass side="left" to anchor the panel to the left edge. The panel still
translates the same distance, but in the opposite direction. Use the
left variant when the trigger or related context lives on the right half
of the page, so the panel appears on the side nearest to the user’s
attention.
Width
The default width is w-175 (43.75rem) which suits a form or a detail
view. Override widthClassName when the contents want more room, for
example a diff, a request inspector, or a wide table. Use a responsive
clamp (w-[min(100vw-1.5rem,64rem)]) so the panel doesn’t overflow on
narrow viewports.
Top offset
topOffset pushes the panel down by a pixel amount, and shrinks its
height to match. Use it when the application renders a fixed page header
or tab bar that the panel should visually sit underneath, so the top of
the panel aligns with the start of the underlying content instead of
overlapping the chrome above it.
Accessibility
The panel is a controlled component: the parent owns isOpen and
onClose, and SlidePanel wires up two dismissal paths on top of whatever
the parent does. Pressing Escape anywhere on the document calls
onClose, and clicking the backdrop does the same. Neither listener is
attached while the panel is closed, so global keybindings elsewhere on
the page are unaffected.
While isOpen is false, the panel surface carries aria-hidden="true"
and the inert attribute, which removes it from the accessibility tree
and skips every descendant when tabbing. The animation uses a CSS
transform rather than unmounting, so assistive tech sees a clean open/
close rather than a DOM churn each time.
SlidePanel also sets document.body.dataset.slidePanelOpen as a
reference-counted marker while any panel is open. Global CSS uses that
hook to lift portaled floating UI (like Tooltips) above the panel’s
z-51 stacking context.
Provide a label on SlidePanel.Close (for example
aria-label="Close panel") when the button is icon-only. The header is
a plain flex row, so consumers are responsible for marking up the panel
title with the right heading level for their page.
Props
SlidePanel.Root
Portals its children into document.body, renders the backdrop and the
sliding surface, and provides the close context to descendants.
isOpen boolean Required Controlled open state. The panel animates in when this flips to
true and out when it flips back to false.
onClose () => void Required Called when the user presses Escape, clicks the backdrop, or
activates a SlidePanel.Close button. The parent is responsible for
flipping isOpen back to false.
side "left" | "right" Optional
Default "right" Which edge the panel slides in from. right is the default; left
mirrors all transforms.
topOffset number Optional
Default 0 Pixel offset from the top of the viewport. The panel’s top edge sits
at topOffset + 12 and its height shrinks to match, so the panel
fits under a fixed header without overlapping it.
widthClassName string Optional
Default "w-175" Tailwind width class for the panel surface. Replace the default when the contents need more or less room than 43.75rem.
className string Optional Extra Tailwind classes merged onto the panel surface via cn.
Useful for per-instance padding, background, or shadow overrides.
children ReactNode Optional The panel tree. Typically a SlidePanel.Header,
SlidePanel.Content, and optional SlidePanel.Footer.
SlidePanel.Header
A horizontal flex row with a bottom border and 2rem horizontal padding. Holds the title block and a dismiss control.
className string Optional Additional Tailwind classes. Override spacing, alignment, or background when the default chrome does not fit.
children ReactNode Optional Usually the panel title and description on one side, and a
SlidePanel.Close button on the other.
SlidePanel.Content
The flexible middle region. Expands to fill the space between header and footer, and by default staggers in from the right with a fade so the content lands slightly after the panel itself.
stagger boolean Optional
Default true When true, the content slides and fades in after the panel. Set to
false for content that should appear immediately (for example,
when the panel is used as a persistent drawer).
staggerDelay number Optional
Default 150 Delay in milliseconds between the panel opening and the content
animating in. Ignored when stagger is false.
className string Optional Additional Tailwind classes. Commonly used to add internal padding
or make the content area scroll (overflow-y-auto).
children ReactNode Optional The body of the panel.
SlidePanel.Footer
A fixed-height row at the bottom of the panel, with a top border and 2rem horizontal padding. Use it for primary and secondary actions.
className string Optional Additional Tailwind classes. Typical overrides are justify-end or
justify-between depending on how the footer actions are arranged.
children ReactNode Optional Usually one or two action <Button>s.
SlidePanel.Close
A <button type="button"> that pulls onClose from context and calls
it on click. Place one in the header, or anywhere else the user should
be able to dismiss the panel from.
onClick (event: MouseEvent<HTMLButtonElement>) => void Optional Optional handler that runs before onClose. Call
event.preventDefault() inside it to stop the panel from closing
(for example, to confirm unsaved changes first).
...rest ButtonHTMLAttributes<HTMLButtonElement> Optional All standard button attributes pass through, including className,
aria-label, and disabled.
Related
- Drawer — a full-height sheet that covers the edge of the viewport, for flows that should feel modal and capture full attention.
- Drover — a fuller takeover panel for multi-step flows where the surrounding context should fade away entirely.
- Dialog — for short, focused decisions that block the page.