ui
ui copied to clipboard
Dialog Overflow Behavior
When using the dialog with content larger than the screen height, the dialog gets cut off on top and bottom without the ability to scroll.
Steps to reproduce: Put component with height > screen height inside of Dialog. Intended behavior: Scroll
First ever Issue, sorry if its unclear. Happy to elaborate. Thanks a lot for making this open source. It is invaluable!
I thought it might help to add a repo demonstrating the issue, so I threw one together. It's a fresh install, modifying the index page to have the Dialog component with an inline style of 5000px.
https://github.com/joshwaiam/ui.shadcn.com-dialog-overflow-issue
Is this a Radix UI issue or?
Workaround:
I fixed this problem for myself by adding overflow-y-scroll max-h-screen
classes to DialogContent
example usage:
<Dialog>
<DialogTrigger asChild>
<Button variant={"ghost"} title={"Text""} />
</DialogTrigger>
<DialogContent className={"lg:max-w-screen-lg overflow-y-scroll max-h-screen"}>
todo...
</DialogContent>
</Dialog>
I had a similar problem with the sheet component and too much content, but the overflow-y-scroll max-h-screen
fix worked there too. Thanks a lot!
I always had the issue with the scrollbar on the right side of the content.
There is a solution by RadixUI that worked fine for me: https://www.radix-ui.com/docs/primitives/components/dialog#scrollable-overlay
I enables scrolling the background thus "moving" the dialog instead of scrolling inside which I found to be a nicer interaction. You need to move the content inside the overlay and change the way it handles the positioning of the content. (I removed some of the animations because I was too lazy to adjust them)
DialogOverlay
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/30 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 overflow-y-auto max-h-screen grid place-items-center",
className
)}
{...props}
/>
DialogContent
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
className={cn(
"z-50 relative grid w-full max-w-lg gap-4 bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg md:w-full",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
edit: Content needs to be set to relative so the closing-button gets rendered on the content. otherwise its at the top right of the overlay
@max-hans If I try to replicate your example -> I get a darkened screen without the Dialog being displayed at all. I tried following https://www.radix-ui.com/docs/primitives/components/dialog#scrollable-overlay , but It did not show the Dialog too. Maybe I'am doing something wrong?
DialogOverlay
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 ", // overlay
"bg-black/50 backdrop-blur-sm", // bg
"transition-all duration-100 data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out", // animations
"grid place-items-center overflow-y-auto", // layout children
className,
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
DialogContent
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
// shadcn / Dialog Overflow Behavior - https://github.com/shadcn/ui/issues/16
// radix-ui / Scrollable overlay - https://www.radix-ui.com/docs/primitives/components/dialog#scrollable-overlay
// radix-ui / dialog src - https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/src/Dialog.tsx
className={cn(
"z-50", // overlay
"relative",
"min-width-[300px]",
"grid gap-4 ", // container
"bg-white dark:bg-slate-900", // bg
"p-6 rounded-b-lg sm:rounded-lg", // border
"w-full sm:max-w-screen-sm md:max-w-screen-md", // width
// "overflow-y-scroll max-h-screen", // prevent overflow / add scrolling -> produces scrollbar even if not needed
"animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0", // animations
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
I will try to create something that works. Maybe I changes something else that seems unrelated at first. But I think I just followed the guide by radix
I started from a blank Next13 template and tried to recreate it. To make it overflow nicely I just had to adjust the components inside the dialog.tsx created by shadcn. Your issues might be due to the way the dialog is positioned. It initially used some css-transform to center it on the screen. I couldnt find anything in your code on a first glance. Or you are using the Dialog inside some other element that is positioned relative affecting the way the absolute elements inside the dialog component are positioned?
Should we maybe add the Link to the radix documentation regarding overflow somewhere in the docs?
This is my dialog-component:
"use client";
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = ({
className,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props} />
);
DialogPortal.displayName = DialogPrimitive.Portal.displayName;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/30 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 overflow-y-auto py-16 grid place-items-center",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
className={cn(
"z-50 grid w-full max-w-lg gap-4 bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg md:w-full relative",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
thanks @intuity-hans I can confirm your full version is working
Comparing mine and your version, I think you forgot to mention the changes in DialogPortal
DialogPortal
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">{children}</div>
</DialogPrimitive.Portal>
)
im have same issue
i had same error so i tried to solve my way and this actually work i did this by removing the style of pointer events like so:
` onClose: () => { set({ type: null, isOpen: false });
// Delay restoring pointer events for 2 seconds
setTimeout(() => {
document.body.style.pointerEvents = "auto";
}, 1000);
}, }));`
Note, i did it with small timeout to make sure that first all modal scripts are completed and then im manually removing that. Hope it helps!
Is this still an issue? Does @intuity-hans solution fix this?
I can confirm, that
- the current dialog component from the CLI is not scrollable
- @intuity-hans version fixed the issue for me
I however also found that the @intuity-hans dialog always leaves some space to the top border of the screen + it loses the fullscreen snapping functionality in general.
I can look deeper into this and create a PR
Workaround: I fixed this problem for myself by adding
overflow-y-scroll max-h-screen
classes toDialogContent
example usage:
<Dialog> <DialogTrigger asChild> <Button variant={"ghost"} title={"Text""} /> </DialogTrigger> <DialogContent className={"lg:max-w-screen-lg overflow-y-scroll max-h-screen"}> todo... </DialogContent> </Dialog>
This is a really nice workaround because it is not so much invasive, liked this approach!
overflow-y-scroll
To complement this solution, you may want to hide the vertical scrollbars without compromising on the scroll functionality. To do so, declare the following two CSS classes and use them as an additional class.
`/* For Webkit-based browsers (Chrome, Safari and Opera) */ .scrollbar-hide::-webkit-scrollbar { display: none; }
/* For IE, Edge and Firefox / .scrollbar-hide { -ms-overflow-style: none; / IE and Edge / scrollbar-width: none; / Firefox */ }`
I'm having scroll issue with flexbox inside DialogContent
For example:
<DialogContent className='h-full'>
<div className='h-full flex flex-col'>
<div>Title</div>
<div className='min-h-0 flex-1 overflow-y-auto'>
<div className='h-[1000px]'>Long long long content</div>
</div>
</div>
</DialogContent>
The long content should be scollable when the screen is smaller than 1000px - Title height but it's not.
The most simple solution is making DialogContent
block
instead of grid
<DialogContent className='block h-full'>
<div className='w-full h-full flex flex-col gap-4'>
<div>Title</div>
<div className='min-h-0 flex-1 overflow-y-auto'>
<div className='h-[1000px]'>Long long long content</div>
</div>
</div>
</DialogContent>
Are there any specify reason why the DialogContent
is grid
?
since it's content only have 1 element {children}
and also have no grid related styling.
Managed to solve this based on the solution by @intuity-hans.
The small detail that made the difference was to render DialogContent
inside DialogOverlay
, instead of rendering them side-by-side as it is currently described in the docs:
const DialogContent = (...) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content ... >
...
</DialogPrimitive.Content>
</DialogPortal>
)
so I changed to this:
const DialogContent = (...) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content ... >
...
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
)
hmmm. this is still an issue for me. I need to make the height of the dialog fit content and also have a max height. i reckon this is a common need.
I found it quite frustrating that the footer and header are not fixed while the content is scrollable. For everyone that wants to achieve something similar as on my attached video
https://github.com/shadcn-ui/ui/assets/7522096/395352f9-5f4f-446b-aa10-d302cd4c9e88
Switch grid
for flex flex-col
in the DialogPrimitive.Content classnames
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 flex w-full max-w-lg translate-x-[-50%] translate-y-[-50%] flex-col gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}
>
Example Dialog
<Dialog>
<DialogTrigger asChild>
Show Dialog
</DialogTrigger>
// adding max-h-[98%] so the modal is not sticking to the borders, but stays within the screen height
<DialogContent className="max-h-[98%] overflow-hidden px-0">
<DialogHeader className="px-6">
<DialogTitle>Filters</DialogTitle>
</DialogHeader>
// making the content to take all height available with flex-1 and scrollable
<div className="flex-1 overflow-y-auto px-6 py-6">
// your content here
</div>
<DialogFooter className="px-6">
<Button variant="secondary">Reset all filters</Button>
<Button>Apply filters</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Enjoy!
Use shadcn ScrollArea component.
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-lg p-0">
<ScrollArea className="max-h-[80vh] p-6">
<DialogHeader>
<DialogTitle>Title</DialogTitle>
</DialogHeader>
<div className="space-y-4 mx-1">
</div>
<DialogFooter>
</DialogFooter>
</ScrollArea>
</DialogContent>
</Dialog>
I found it quite frustrating that the footer and header are not fixed while the content is scrollable. For everyone that wants to achieve something similar as on my attached video
Screen.Recording.2023-12-21.at.3.14.39.PM.mov Switch
grid
forflex flex-col
in the DialogPrimitive.Content classnames<DialogPrimitive.Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 flex w-full max-w-lg translate-x-[-50%] translate-y-[-50%] flex-col gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className, )} {...props} >
Example Dialog
<Dialog> <DialogTrigger asChild> Show Dialog </DialogTrigger> // adding max-h-[98%] so the modal is not sticking to the borders, but stays within the screen height <DialogContent className="max-h-[98%] overflow-hidden px-0"> <DialogHeader className="px-6"> <DialogTitle>Filters</DialogTitle> </DialogHeader> // making the content to take all height available with flex-1 and scrollable <div className="flex-1 overflow-y-auto px-6 py-6"> // your content here </div> <DialogFooter className="px-6"> <Button variant="secondary">Reset all filters</Button> <Button>Apply filters</Button> </DialogFooter> </DialogContent> </Dialog>
Enjoy!
This is ok, but how can i use the styles for the scroll bar that are used in the ScollArea component?
does anyone know why I get this weird bar at the right side of Dialog? and what is the solution for it?
does anyone know why I get this weird bar at the right side of Dialog? and what is the solution for it?
You can hide it like this: https://github.com/shadcn-ui/ui/issues/16#issuecomment-1812929398
Hope it helps :)
does anyone know why I get this weird bar at the right side of Dialog? and what is the solution for it?
You can hide it like this: #16 (comment)
Hope it helps :)
I see, that completely hides the scroll bar even while scrolling, what if we want to show the bar only while scrolling?
does anyone know why I get this weird bar at the right side of Dialog? and what is the solution for it?
using overflow-auto
will fix and only show the scrollbar if necessary.
I had a similar problem with the sheet component and too much content, but the
overflow-y-scroll max-h-screen
fix worked there too. Thanks a lot!
hello, which component exactly did you put this code? I've been trying but doesn't work for me. Cheers!
I had a similar problem with the sheet component and too much content, but the
overflow-y-scroll max-h-screen
fix worked there too. Thanks a lot!hello, which component exactly did you put this code? I've been trying but doesn't work for me. Cheers!
Hello, Were you able to get this to work?
// sheet.tsx
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
// automatically scrolling up bug fixed
onCloseAutoFocus={event => event.preventDefault()}
{...props}
Go to sheet component and add the onCloseAutoFocus
prop and the code I sent.
#this code is not my own, I also just found this #somewhere.
This is the solution to make the entire overlay scrollable without errors, just change the Overlay and the Content as below.
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 grid place-items-center overflow-y-auto data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay className="p-16">
<DialogPrimitive.Content
ref={ref}
className={cn(
"relative border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close asChild>
<Button
size="icon"
variant="ghost"
className="absolute top-3 right-3"
>
<X className="h-5 w-5" />
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;