[v4] Side drawer grows with its content instead of scrolling inside, so `position: sticky` in a Drawer never sticks

Open Beginner friendly
#19,403 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
css, next.js, react, typescript
Domain
frontend

Research direction

Start by reading packages/ui/src/elements/Drawer/index.css and compare it with the v3 drawer stylesheet linked in the issue. The reproduction includes a long drawer and sticky footer in apps/next/components/LongDrawer.tsx; check the computed drawer height and which element scrolls. Done means the drawer fills the viewport and its content area scrolls so the sticky footer stays at the bottom.

Written by the indexing model from the issue text.

Description

area: ui Bug created-by: Contributor status: needs-triage v3
Describe the Bug

Drawer/index.css describes a viewport-tall panel that scrolls inside:

  • .drawer is position: fixed; inset: 0; overflow: hidden;
  • .drawer__content-children is overflow-y: auto; height: 100%.

In the browser, though, a side drawer grows to the height of its content, and the modal container scrolls the whole drawer. .drawer__content-children is still the nearest overflow: auto ancestor, so it's the box position: sticky binds to. But it never scrolls, because its height equals its content. Any sticky element inside a drawer stays in flow:

  • a sticky footer with the drawer's confirm buttons ends up below the fold;
  • a sticky table of contents scrolls away.

Root cause. Three things combine:

  1. The drawer's modal item is a <dialog class="payload__modal-item … drawer">. @faceless-ui/modal's ModalProvider injects an unlayered <style> with .payload__modal-item { position: absolute } (generateCSS.js). Payload's .drawer { position: fixed } lives in @layer payload-default, and unlayered rules beat layered ones regardless of specificity. So the drawer is position: absolute.
  2. Nothing sets the drawer's height, so the UA style dialog { height: fit-content } applies. inset: 0 doesn't stretch it, and the drawer grows to its content.
  3. Payload 3's .drawer had an explicit height: 100vh (3.x Drawer/index.scss), which hid (1) and (2). The v4 rewrite dropped it.

Measured in a side drawer with 60 rows and a position: sticky; bottom: 0 footer, at a 1920×945 viewport:

Element Computed
footer (position: sticky) top 1009, below the 945px viewport
.drawer__content-children (overflow-y: auto) clientHeight 1049, scrollHeight 1049: never scrolls
dialog.payload__modal-item.drawer position: absolute (unlayered faceless rule), height 1049
.payload__modal-container position: fixed, clientHeight 945, scrollHeight 1049: this is what scrolls

Adding @layer payload-default { .drawer { height: 100dvh } } (same layer, no specificity tricks) fixes it:

  • the drawer is 945px tall;
  • .drawer__content-children scrolls (client 945, scroll 1049);
  • the footer sticks at 905–945.

Expected: the drawer is as tall as the viewport and .drawer__content-children scrolls, which is what its overflow-y: auto; height: 100% is for.

Link to the code that reproduces this issue

https://github.com/VeiaG/payload-4-reproductions/tree/repro-drawer-grows-instead-of-scrolling

Reproduction Steps
  • apps/next/components/LongDrawer.tsx (dashboard, beforeDashboard): a "Open long drawer" button that opens a Drawer with 60 rows and a sticky footer (red).
  • long-form / drawer-host collections: a native document drawer with 30 fields (relationship field → "+"), which grows the same way.
  1. pnpm install && pnpm dev, then open http://localhost:3000/admin.
  2. Click Open long drawer.
  3. The red "Sticky footer" isn't at the bottom of the viewport. It sits after the last row, and scrolling moves the whole drawer, header included.
  4. In devtools, dialog.drawer is position: absolute. The winning rule is the unlayered .payload__modal-item from @faceless-ui/modal's injected <style>.
Which area(s) are affected?

area: ui

Environment Info
payload: 4.0.0-beta.7
@payloadcms/ui: 4.0.0-beta.7
@faceless-ui/modal: 3.0.2
next: 16.4.4
Browser: Chrome
Dominant language
TypeScript
Stars
45.6k
Forks
4.4k
Avg merge
1d 23h
Merged PRs (30d)
237

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from payloadcms/payload

All issues in payloadcms/payload

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.