I've been using shadcn/ui for a while now, and I’ve got to say it’s an incredible tool for building clean, accessible interfaces in React. However, I recently ran into an annoying issue: the page freezes after interacting with certain components like Dialog, Dropdown Menu, Sheet, or Popover usually right after they close. The only way to get things working again was to refresh the page.
If this sounds familiar, don’t worry you’re not alone. In this article, I’ll walk you through why this happens and how to fix it so your app runs smoothly without needing a page reload. Let’s dive in!
Why the Page Freezes After Closing a Dialog
The Dialog component from shadcn/ui (built on top of Radix UI) traps focus inside the modal for accessibility reasons. When the dialog closes, focus should return to the previously focused element.
However, your page may freeze or stop responding if the dialog remains mounted in the DOM even after closing.
Fixing the Page Freeze
To fix the page freeze, all you need to do is add modal={false} on your dialog component
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function DialogFreezeFix () {
return (
<Dialog> // add it here
<form>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're
done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-3">
<Label htmlFor="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
)
}
After adding it, you'll have something like this:
<Dialog modal={false}> // there you go
<form>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're
done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-3">
<Label htmlFor="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
This also works for the DropdownMenu, Sheet, and Popover components.
Handling it with a state
You can also ensure it is handled by managing the state of the component:
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function DialogFreezeFix () {
const [open, setOpen] = useState(false);
return (
<Dialog
open={open}
onOpenChange={setOpen}
modal={false} // ensure you still include the modal prop
>
<form>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're
done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-3">
<Label htmlFor="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
)
}
Conclusion
The freezing issue usually stems from an uncontrolled dialog lifecycle or focus trap.
By managing open state and modal prop explicitly and ensuring proper unmounting, your page will stay smooth and responsive even after the dialog closes.
If you found this helpful, drop a ❤️ on Dev.to and also follow on LinkedIn.
Top comments (0)