A right-anchored slide-over with backdrop, sticky header (title + subtitle + close), scrollable body, and an optional footer for action buttons. Smooth slide + fade transitions; Esc-to-dismiss; controlled via open/onClose. Inspired by Stripe's customer detail drawer.
cus_8x4Klp9 · Customer since Mar 2024
Lifetime value
$42,318.20
Plan
Pro · annualRecent activity
Backdrop click or Esc dismisses · Smooth slide-in from the right · Sticky header + scrollable body + footer
// Dependencies: react ^18, lucide-react
import React, { useEffect, ReactNode } from 'react';
import { X } from 'lucide-react';
function cn(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
type Props = {
open: boolean;
onClose: () => void;
title: string;
subtitle?: string;
children: ReactNode;
footer?: ReactNode;
};
export function SlideOverPanel({ open, onClose, title, subtitle, children, footer }: Props) {
useEffect(() => {
if (!open) return;
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [open, onClose]);
return (
<div
className={cn(
'fixed inset-0 z-50 transition-opacity duration-200',
open ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
)}
role="dialog"
aria-modal="true"
aria-hidden={!open}
>
{/* Backdrop */}
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
{/* Panel */}
<div
className={cn(
'absolute right-0 top-0 h-full w-full max-w-[440px] bg-zinc-900 border-l border-white/10 shadow-2xl flex flex-col transition-transform duration-300 ease-out',
open ? 'translate-x-0' : 'translate-x-full'
)}
>
<div className="flex items-start justify-between gap-3 px-5 py-4 border-b border-white/[0.08] shrink-0">
<div className="min-w-0">
<h2 className="text-sm font-semibold text-white truncate">{title}</h2>
{subtitle && <p className="text-xs text-zinc-500 mt-0.5 truncate">{subtitle}</p>}
</div>
<button
onClick={onClose}
aria-label="Close"
className="text-zinc-500 hover:text-zinc-200 transition-colors shrink-0"
>
<X className="w-4 h-4" />
</button>
</div>
<div className="flex-1 overflow-y-auto px-5 py-5">{children}</div>
{footer && (
<div className="px-5 py-3 border-t border-white/[0.08] flex items-center justify-end gap-2 shrink-0">
{footer}
</div>
)}
</div>
</div>
);
}Unlock to copy
Free access to all patterns
Command Palette
Linear
Global Search with Results Grouping
Linear
Recent + Suggested Search
Raycast
Filter Builder (AND / OR)
Linear
Sidebar Navigation
Linear
Collapsible Nested Tree Nav
Notion
Tab Bar with Overflow Menu
Vercel
Breadcrumb with Dropdowns
Linear
Workspace Switcher
Slack
Deployment Status Card
Vercel
Empty State
Vercel
Slash Command Menu
Notion
Stats & Metrics Row
Vercel
Single Big Metric Card
Stripe
Confirmation Dialog
Linear
Slide-Over Panel
Stripe
Issue / Task Card
Linear
Activity Feed
Linear + Slack
Properties Panel
Notion + Linear
Inline Data Table
Notion + Linear