- Published on
How to Retain Close Button Functionality in Shadcn Dialogs ?
- Authors
- Name
- Ripal & Zalak
When working with Shadcn or Radix dialogs in React, especially in Next.js, using the open
prop for programmatic control can sometimes conflict with the built-in close button functionality. This guide explains how to ensure the dialog’s close button continues to function properly while maintaining programmatic control.
The Problem
When you use the open
prop to control a Shadcn dialog, you might find that the close button (the X
in the top-right corner) stops working. This happens because the dialog's open state is now controlled entirely by the parent component, and the close button no longer triggers a state change.
Example
Here’s a typical example of a dialog using the open
prop:
const [isOpen, setIsOpen] = useState(false)
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger>Open Dialog</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Content goes here</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
In this setup, the dialog's open state is tied to the isOpen
state. Clicking the close button will not update this state unless explicitly handled.
onOpenChange
Solution: Use To fix this, you need to use the onOpenChange
prop to handle state changes triggered by the close button or other user interactions (e.g., clicking outside the dialog). Here’s the updated code:
const [isOpen, setIsOpen] = useState(false)
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger>Open Dialog</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Content goes here</DialogDescription>
</DialogHeader>
<p>Some more content here.</p>
</DialogContent>
</Dialog>
)
Why This Works
The onOpenChange
prop ensures that state changes triggered by the close button (or other interactive elements) are communicated back to your component. The setIsOpen
function is called automatically with the new state, keeping your component in sync with the dialog.
Additional Considerations
Custom Close Behavior: If you want to add custom behavior when the close button is clicked, you can extend the
onOpenChange
handler:const handleDialogClose = (open: boolean) => { if (!open) { // Perform additional actions, e.g., cleanup or analytics console.log('Dialog closed') } setIsOpen(open) } return ( <Dialog open={isOpen} onOpenChange={handleDialogClose}> <DialogTrigger>Open Dialog</DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Dialog Title</DialogTitle> <DialogDescription>Content goes here</DialogDescription> </DialogHeader> </DialogContent> </Dialog> )
Prevent Closing on Outside Click: If you want to prevent the dialog from closing when clicking outside, you can customize the dialog behavior:
<Dialog open={isOpen} onOpenChange={(open) => setIsOpen(open)}> <DialogContent onInteractOutside={(e) => e.preventDefault()}> <DialogHeader> <DialogTitle>Dialog Title</DialogTitle> </DialogHeader> </DialogContent> </Dialog>
Keyboard Accessibility: Radix dialogs are keyboard accessible by default, meaning users can press
Esc
to close the dialog. Ensure your state management supports this behavior.
Conclusion
By combining the open
prop with the onOpenChange
handler, you can maintain programmatic control over the dialog while ensuring that the close button and other interactive elements work as expected. This approach provides a seamless user experience and avoids potential issues with state synchronization.