Side Dialog

A lightweight dialog that opens from the sides of the screen, perfect for notifications, user menus, and contextual actions.

Installation

npx shadcn@latest add https://shuip.xyz/r/side-dialog.json

Preview

Contextual positioning

Unlike traditional centered dialogs, SideDialog provides a more contextual user experience by opening from the edges of the screen, perfect for notifications, user menus, and contextual actions.

Built-in features

  • 6 positions: Choose from top-left, top-right, bottom-left, bottom-right, left, right
  • Flexible sizes: Predefined sizes (xs, sm, md, lg, xl, 2xl)
  • Lightweight: Custom implementation without depending on shadcn dialog
  • Accessible: Keyboard support (Escape), focus management, and ARIA compliance
  • Portal rendering: Renders in document.body to avoid z-index issues
  • Smooth animations: Entry/exit animations with Tailwind CSS
  • Controlled & Uncontrolled: Works with or without state management

Position options

The position prop controls where the dialog appears:

// Corner positions
<SideDialog position="top-left"> {/* Top left corner */}
<SideDialog position="top-right"> {/* Top right corner */}
<SideDialog position="bottom-left"> {/* Bottom left corner */}
<SideDialog position="bottom-right"> {/* Bottom right corner (default) */}
// Side positions (vertically centered)
<SideDialog position="left"> {/* Left side, centered */}
<SideDialog position="right"> {/* Right side, centered */}

Size options

The size prop controls the dialog size with predefined responsive values:

// Predefined sizes (all automatically responsive)
<SideDialog size="xs"> {/* 320px max-width, adapts to small screens */}
<SideDialog size="sm"> {/* 384px max-width (default) */}
<SideDialog size="md"> {/* 448px max-width */}
<SideDialog size="lg"> {/* 512px max-width */}
<SideDialog size="xl"> {/* 576px max-width */}
<SideDialog size="2xl"> {/* 672px max-width */}
// For custom sizes, use className with responsive constraints
<SideDialogContent className="w-full max-w-[min(400px,calc(100vw-2rem))]">
{/* Custom size with responsive protection */}
</SideDialogContent>

Responsive behavior

All predefined sizes automatically include responsive constraints using CSS min() function:

  • Desktop: Uses the specified max-width (e.g. lg = 32rem/512px)
  • Small screens: Automatically reduces to calc(100vw - 2rem) when viewport is smaller
  • Never overflows: The dialog will always fit within the viewport with 1rem margin on each side
  • CSS native: Uses min(targetSize, calc(100vw - 2rem)) for optimal performance

Common use cases

Notifications and alerts

// Success notification - top right
<SideDialog position="top-right">
<SideDialogTrigger asChild>
<Button onClick={handleSave}>Save Changes</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle className="text-green-600">Success!</SideDialogTitle>
<SideDialogDescription>Your changes have been saved.</SideDialogDescription>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// Error notification - top left
<SideDialog position="top-left">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle className="text-red-600">Error</SideDialogTitle>
<SideDialogDescription>Something went wrong. Please try again.</SideDialogDescription>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>

Different sizes for different content

// Small notification - xs size
<SideDialog position="top-right" size="xs">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Saved!</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// User profile - medium size
<SideDialog position="top-right" size="md">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>User Profile</SideDialogTitle>
</SideDialogHeader>
<div className="space-y-4">
<div className="flex items-center space-x-4">
<Avatar className="w-16 h-16">
<AvatarImage src="/avatar.jpg" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold">John Doe</h3>
<p className="text-muted-foreground">john@example.com</p>
</div>
</div>
</div>
</SideDialogContent>
</SideDialog>
// Wide content panel - custom size with responsive constraints
<SideDialog position="right">
<SideDialogContent className="w-full max-w-[min(600px,calc(100vw-2rem))]">
<SideDialogHeader>
<SideDialogTitle>Content Editor</SideDialogTitle>
</SideDialogHeader>
<div className="space-y-4">
<Textarea placeholder="Write your content here..." rows={10} />
<div className="flex justify-end space-x-2">
<Button variant="outline">Save Draft</Button>
<Button>Publish</Button>
</div>
</div>
</SideDialogContent>
</SideDialog>

Help and information

// Help panel - bottom left
<SideDialog position="bottom-left">
<SideDialogTrigger asChild>
<Button variant="outline" size="icon">
<HelpCircle className="size-4" />
</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Need Help?</SideDialogTitle>
<SideDialogDescription>
Find answers to common questions or contact support.
</SideDialogDescription>
</SideDialogHeader>
<div className="space-y-3">
<Button variant="outline" className="w-full">
<Book className="mr-2 size-4" />
Documentation
</Button>
<Button variant="outline" className="w-full">
<MessageCircle className="mr-2 size-4" />
Contact Support
</Button>
</div>
</SideDialogContent>
</SideDialog>

State management

The component supports both controlled and uncontrolled modes, automatically detecting which mode to use based on the props provided:

// Uncontrolled: Component manages its own state
<SideDialog position="top-right">
<SideDialogTrigger asChild>
<Button>Open</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Self-managed State</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// Controlled: You manage the state
const [open, setOpen] = useState(false);
<SideDialog open={open} onOpenChange={setOpen} position="top-right">
<SideDialogTrigger asChild>
<Button>Open</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Controlled State</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>

Advanced customization

Custom close behavior

// Disable the default close button
<SideDialogContent showCloseButton={false}>
<SideDialogHeader>
<SideDialogTitle>Custom Controls</SideDialogTitle>
</SideDialogHeader>
<SideDialogFooter>
<SideDialogClose asChild>
<Button variant="destructive">Cancel</Button>
</SideDialogClose>
<SideDialogClose asChild>
<Button>Confirm</Button>
</SideDialogClose>
</SideDialogFooter>
</SideDialogContent>

Custom styling

// Custom dialog appearance
<SideDialogContent className="border-primary shadow-xl bg-gradient-to-b from-background to-muted">
<SideDialogHeader>
<SideDialogTitle className="text-primary">Custom Style</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>

Examples

Default

Form

Props

NameTypeDescription
openboolean?Controlled open state (optional)
onOpenChange(open: boolean) => void?Callback when open state changes
positionSideDialogPosition?Position of the dialog: "top-left" | "top-right" | "bottom-left" | "bottom-right" | "left" | "right" (default: "bottom-right")
sizeSideDialogSize?Size of the dialog: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" with automatic responsive constraints (default: "sm")
childrenReact.ReactNodeDialog content (SideDialogTrigger and SideDialogContent)

SideDialogContent Props:

Examples

No examples

Props

NameTypeDescription
showCloseButtonboolean?Whether to show the default close button (default: true)
classNamestring?Additional CSS classes
childrenReact.ReactNodeDialog content

SideDialogTrigger Props:

Examples

No examples

Props

NameTypeDescription
asChildboolean?Render as child element instead of button
childrenReact.ReactNodeTrigger content
...propsButtonHTMLAttributes<HTMLButtonElement>All button HTML attributes