Web Development

shadcn ScrollArea Not Scrolling in Dialog: Fix [2026]

Asep Alazhari

shadcn ScrollArea not scrolling inside a Dialog? The viewport needs a real height. Here is why max-h fails and the overflow-y-auto fix the docs now use.

shadcn ScrollArea Not Scrolling in Dialog: Fix [2026]

If your shadcn ScrollArea is not scrolling inside a Dialog, the list renders, the mouse wheel does nothing, and the rows below the fold are simply unreachable. The cause is a CSS height problem, not a bug in Radix or Base UI. The ScrollArea viewport uses height: 100%, and a percentage height only works when its parent has a definite height. A max-h class is not a definite height, so the viewport grows to fit every row and there is nothing left to scroll.

The quickest fix is to drop ScrollArea for this case and use a plain overflow-y-auto container with a max-h. That is exactly what the current shadcn/ui Dialog docs do in their scrollable content example.

Quick Fix

Replace the ScrollArea wrapper with a native scroll container.

// Before: renders every row, never scrolls
<ScrollArea className="max-h-[400px]">
    {items.map((item) => (
        <ItemRow key={item.id} item={item} />
    ))}
</ScrollArea>

// After: scrolls once the list passes half the screen height
<div className="-mx-6 max-h-[50vh] overflow-y-auto px-6">
    {items.map((item) => (
        <ItemRow key={item.id} item={item} />
    ))}
</div>

The negative margin and matching padding push the scrollbar to the edge of the dialog instead of leaving it floating next to your content. Use -mx-4 px-4 if your DialogContent uses p-4.

Why shadcn ScrollArea Is Not Scrolling in Your Dialog

The ScrollArea component shadcn ships is only a thin wrapper, and the viewport inside it is told to fill its parent. Here is the relevant part of scroll-area.tsx from the shadcn/ui registry as of September 2026.

<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)}>
    <ScrollAreaPrimitive.Viewport data-slot="scroll-area-viewport" className="size-full rounded-[inherit] ...">
        {children}
    </ScrollAreaPrimitive.Viewport>
    <ScrollBar />
</ScrollAreaPrimitive.Root>

size-full compiles to width: 100%; height: 100%. According to the MDN reference for height, a percentage height resolves against the parent’s height only if that height is definite. When the Root only has max-h-[400px], its height is still auto, so 100% of it also behaves as auto. The viewport becomes as tall as all of its rows. Its clientHeight equals its scrollHeight, and a box that is exactly as tall as its content has nothing to scroll.

The Dialog makes it worse. The default DialogContent in the Radix variant is fixed top-[50%] translate-y-[-50%] grid gap-4 p-6 sm:max-w-lg. There is no max height anywhere. So when the list grows, the dialog grows with it, and because it is centered with a translate, it gets clipped equally at the top and the bottom of the window. That is how you lose both the close button and the footer buttons at the same time.

This is also why the bug does not change with the July 2026 switch that made Base UI the default in shadcn/ui. I checked both registry variants, and the Base UI ScrollArea viewport uses the same size-full class. Different primitive, same CSS rule. It also explains why shadcn/ui issue #922, titled “ScrollArea doesn’t work in Dialog”, keeps collecting reports years after it was opened.

Also Read: shadcn/ui vs Chakra UI vs MUI: Which to Pick in 2026?

Confirm It in DevTools in 10 Seconds

Open the dialog, then paste this into the browser console.

const vp = document.querySelector('[data-slot="scroll-area-viewport"]');
console.log({ clientHeight: vp.clientHeight, scrollHeight: vp.scrollHeight });

If the two numbers are equal, the viewport has no height constraint and this article is your problem. If scrollHeight is larger but scrolling still fails, look at the Popover section further down instead.

Fix 1: Use overflow-y-auto With a Max Height

A native scroll container is the right default for lists whose length changes. It works because overflow-y: auto combined with max-height does not need a definite parent height. The box grows with its content until it hits the cap, then scrolls.

That behavior is the whole point. A short list of three items gets a short box. A list of 154 items gets capped and scrolls. ScrollArea with a fixed h-[400px] cannot do that, since three items would sit inside a mostly empty 400 pixel panel.

The shadcn/ui Dialog documentation now uses this pattern for its scrollable content example, in the Radix, Base UI, and React Aria variants alike. It is a plain div with max-h-[50vh] overflow-y-auto, not ScrollArea.

Fix 1 scrolls the list, but a dialog with a long form above the list can still outgrow the screen. The robust layout caps the whole dialog and lets only the body scroll.

<DialogContent className="flex max-h-[90dvh] flex-col gap-0 p-0 sm:max-w-lg">
    <DialogHeader className="shrink-0 border-b p-6">
        <DialogTitle>Bulk edit</DialogTitle>
        <DialogDescription>{selectedIds.length} items selected</DialogDescription>
    </DialogHeader>

    <div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">
        {items.map((item) => (
            <ItemRow key={item.id} item={item} />
        ))}
    </div>

    <DialogFooter className="shrink-0 border-t p-6">
        <Button variant="outline" onClick={onCancel}>
            Cancel
        </Button>
        <Button onClick={onSubmit}>Apply</Button>
    </DialogFooter>
</DialogContent>

Four details make this work.

  1. flex flex-col replaces the default grid. The cn helper uses tailwind-merge, so the later display class wins cleanly.
  2. max-h-[90dvh] caps the dialog. The dvh unit tracks the visible viewport on mobile browsers, so the footer does not hide behind a collapsing address bar the way it can with vh.
  3. shrink-0 on the header and footer stops them from being squeezed when space runs out.
  4. min-h-0 flex-1 on the body lets it take the remaining space and shrink below its content height.

The close button stays reachable too, because it is absolutely positioned inside DialogContent, which now never exceeds 90 percent of the screen.

The min-h-0 Gotcha

Without min-h-0, Fix 2 silently fails in the same way ScrollArea did. As the MDN min-height reference describes, flex items default to min-height: auto, which means a flex child refuses to become shorter than its content. So flex-1 stretches the body, but it never shrinks it below the full height of 154 rows, and overflow-y-auto again has nothing to do.

If you ever build a flex column that should scroll and it does not, check for a missing min-h-0 before anything else. It is the most common reason I see, in dialogs, sidebars, and chat panels.

This catches a lot of people coming from Bootstrap, where a .modal-dialog-scrollable class handles the whole layout for you. If you are in the middle of that move, my Bootstrap to Tailwind CSS migration notes cover the rest of the switch.

When ScrollArea Is Still the Right Choice

ScrollArea is fine when you actually want its custom styled scrollbar and you can give it a real height. The shadcn ScrollArea docs are explicit about this, and their example uses h-[200px].

SituationUse
List length varies, from a few rows to hundredsdiv with max-h and overflow-y-auto
Fixed size panel, consistent custom scrollbarScrollArea with h-72 or h-[min(400px,50vh)]
Whole dialog could outgrow the screenFix 2, capped flex column with a scrolling body
Horizontal scroll in a Base UI ScrollAreaWrap children in ScrollArea.Content

If you replace ScrollArea mainly for layout reasons but miss the thinner scrollbar, add [scrollbar-width:thin] to the native container. Note that no-scrollbar, used in the shadcn docs example, is a custom utility in their project and not a built in Tailwind class.

Side Note: Popover or Combobox Lists Inside a Dialog

A different bug looks similar. A Popover or Combobox opened from inside a Dialog shows a scrollable list, scrollHeight is clearly larger than clientHeight, yet the wheel still does nothing. That one is caused by the Dialog’s scroll lock, which blocks wheel events on content portaled outside the dialog. On the Radix variant, the fix reported most often in the shadcn/ui GitHub issues is setting modal on the Popover so it manages its own scroll lock. It is not a height problem, so none of the fixes above will help it.

How I Ran Into This

I hit this in an admin dashboard while building a bulk edit dialog. It listed 154 selectable items as a checkbox list, all preselected, wrapped in ScrollArea with a max height. Only the rows that fit on screen were visible. There was no scrollbar and the wheel did nothing, so users could not deselect anything below the fold, and on smaller laptop screens the footer with the Apply button was cut off as well.

The first change swapped ScrollArea for overflow-y-auto, and the list finally scrolled. It still failed on short screens, because the form fields above the list pushed the dialog past the window height. The second change was the capped flex layout from Fix 2. After that, the header, the close button, and the footer stayed on screen no matter how many items were loaded.

Also Read: Handling 429 Rate Limits in Bulk API Requests

Which Fix Should You Use?

Start with this decision rule. If the scroll region has a variable length, use a native overflow-y-auto container with max-h. If the dialog also contains other content that can grow, use the capped flex column from Fix 2, and never forget min-h-0 on the body. Keep ScrollArea for fixed height panels where the custom scrollbar is worth an explicit height.

Frequently Asked Questions

Does this bug still happen with Base UI in shadcn/ui?

Yes. As of September 2026, both the Radix and Base UI versions of the shadcn ScrollArea use a size-full viewport, so both need a parent with a definite height. Switching primitives does not fix it.

Why does max-h not work on ScrollArea but works on a normal div?

A normal div with max-h and overflow-y-auto scrolls itself, so it only needs its own cap. ScrollArea scrolls an inner viewport sized with height: 100%, and a percentage of a max-height box resolves to auto.

Should I use vh or dvh for the dialog max height?

Use dvh. It follows the visible viewport as mobile browser toolbars appear and disappear, while vh uses the largest size and can push the footer off screen on phones.

Can I keep ScrollArea and still make it flexible?

Yes, give it an explicit height that adapts, such as h-[min(400px,50vh)]. The trade-off is that short lists still get the full box height with empty space below.

Back to Blog

Related Posts

View All Posts »