ui
ui copied to clipboard
[BUG] Blurry Content on Low-Resolution Screens with Overflow-Auto in AlertDialog/Dialog Components
Description
The content within AlertDialog
/Dialog
components becomes blurry on low-resolution screens when the overflow-auto
property is in effect. This issue appears to be related to the use of translate
on DialogContent
. The blurring effect is noticeable when the content exceeds the container size, suggesting that the issue may be linked to subpixel rendering and the rasterization process on low-resolution displays.
Environment
- Tailwind CSS version: 3.4.1
- Browser and version: Chrome 119.0.6045.200
- Screen resolution: 1920*1080
Steps to Reproduce
- Create an
AlertDialog
/Dialog
component withoverflow-auto
onDialogContent
. - Add enough content inside the
DialogContent
to cause overflow. - Observe the content on a low-resolution screen.
Expected Behavior
Content should remain crisp and clear, without any blurring, regardless of screen resolution.
Actual Behavior
Content becomes blurry on low-resolution screens when overflow-auto
is applied to DialogContent
with a translate
transform.
Additional Information
- The issue seems to be exacerbated by the
translate
transform, potentially due to subpixel positioning on low-resolution screens. - It is observed that the blurring effect is not present on high-resolution screens, which suggests that pixel density plays a role in the problem.
Possible Solutions
- Avoiding the use of
translate
on elements withinoverflow-auto
containers. - Ensuring that element dimensions and translate values are set to even numbers to align with the pixel grid.
- Using
flexbox
orgrid
layouts for centering instead oftranslate
.
I am looking forward to any suggestions or workarounds for this issue. Thank you for your assistance!
Any update on this?
Any update on this?
Changing translate-y-[-50%]
to translate-y-[-53%]
in dialog.tsx
component file removed the blurriness in most cases. it still happens when i change focus on a TinyMCE input I have within the dialog.
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"[&>div]:p-6 overflow-hidden !p-0 pt-[4.5rem] fixed left-[50%] top-[50%] z-20 grid w-full max-w-2xl translate-x-[-50%] translate-y-[-53%] border border-slate-200 bg-white 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%] rounded-md md:w-full dark:border-slate-800 dark:bg-slate-950",
className
)}
data-test="dialog-content"
{...props}
>
{children}
</DialogPrimitive.Content>
</DialogPortal>
))```
Changing
translate-y-[-50%]
totranslate-y-[-53%]
indialog.tsx
component file removed the blurriness in most cases. it still happens when i change focus on a TinyMCE input I have within the dialog.React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> >(({ className, children, ...props }, ref) => ( <DialogPortal> <DialogOverlay /> <DialogPrimitive.Content ref={ref} className={cn( "[&>div]:p-6 overflow-hidden !p-0 pt-[4.5rem] fixed left-[50%] top-[50%] z-20 grid w-full max-w-2xl translate-x-[-50%] translate-y-[-53%] border border-slate-200 bg-white 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%] rounded-md md:w-full dark:border-slate-800 dark:bg-slate-950", className )} data-test="dialog-content" {...props} > {children} </DialogPrimitive.Content> </DialogPortal> ))```
This works (also for the Command component https://github.com/shadcn-ui/ui/issues/1465).
But my question is: why does this work? How did you come up with this? Is there a specific reason, or was it just trial and error?
I am a user of shadcn/svelte which is currently using the same CSS for dialogs. Here is the issue for the svelte version https://github.com/huntabyte/shadcn-svelte/issues/534 I analyzed it and wrote a lengthy comment in that issue.
IMHO it is best to make the DialogPrimitive.Content a child of DialogOverlay and then use "flex" to center the dialog. This way we would rid of the translate function which is the root of the problem.
For those in need, here's the updated original component to use flexbox for centering, instead of translating, which removes the bluriness. The open animation has also been adjusted, so it's the same as before.
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<div className="fixed z-50 inset-0 flex items-center justify-center">
<DialogPrimitive.Content
ref={ref}
className={cn(
"relative grid w-full max-w-lg 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-[-2%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[-2%] sm:rounded-lg",
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>
</div>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
For those in need, here's the updated original component to use flexbox for centering, instead of translating, which removes the bluriness. The open animation has also been adjusted, so it's the same as before.
const DialogContent = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> >(({ className, children, ...props }, ref) => ( <DialogPortal> <DialogOverlay /> <div className="fixed z-50 inset-0 flex items-center justify-center"> <DialogPrimitive.Content ref={ref} className={cn( "relative grid w-full max-w-lg 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-[-2%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[-2%] sm:rounded-lg", 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> </div> </DialogPortal> )); DialogContent.displayName = DialogPrimitive.Content.displayName;
Thanks!
But a little trick is to use a grid and place-content-center
For those in need, here's the updated original component to use flexbox for centering, instead of translating, which removes the bluriness. The open animation has also been adjusted, so it's the same as before.
const DialogContent = React.forwardRef< React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> >(({ className, children, ...props }, ref) => ( <DialogPortal> <DialogOverlay /> <div className="fixed z-50 inset-0 flex items-center justify-center"> <DialogPrimitive.Content ref={ref} className={cn( "relative grid w-full max-w-lg 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-[-2%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[-2%] sm:rounded-lg", 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> </div> </DialogPortal> )); DialogContent.displayName = DialogPrimitive.Content.displayName;
While this does resolve the blurriness, I've found that the closing animation no longer works after wrapping DialogPrimitive.Content
in a div
😕