[v4] Side drawer grows with its content instead of scrolling inside, so `position: sticky` in a Drawer never sticks
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
Describe the Bug
Drawer/index.css describes a viewport-tall panel that scrolls inside:
.drawerisposition: fixed; inset: 0; overflow: hidden;.drawer__content-childrenisoverflow-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:
- The drawer's modal item is a
<dialog class="payload__modal-item … drawer">.@faceless-ui/modal'sModalProviderinjects 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 isposition: absolute. - Nothing sets the drawer's height, so the UA style
dialog { height: fit-content }applies.inset: 0doesn't stretch it, and the drawer grows to its content. - Payload 3's
.drawerhad an explicitheight: 100vh(3.xDrawer/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-childrenscrolls (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 aDrawerwith 60 rows and a sticky footer (red).long-form/drawer-hostcollections: a native document drawer with 30 fields (relationship field → "+"), which grows the same way.
pnpm install && pnpm dev, then open http://localhost:3000/admin.- Click Open long drawer.
- 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.
- In devtools,
dialog.drawerisposition: absolute. The winning rule is the unlayered.payload__modal-itemfrom@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
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from payloadcms/payload
-
plugin-multi-tenant: `isGlobal` collections redirect tenant-scoped users to `/admin` since 3.93.0 (GlobalViewRedirect prop-name mismatch)Possibly taken A pull request linked to this issue is open or already merged. OpenBug plugin: multi-tenant
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#19420 ·
Maintainers usually reply within 1 day
-
area: ui Bug created-by: Contributor status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19413 ·
Maintainers usually reply within 1 day
-
area: ui Bug created-by: Contributor status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19412 ·
Maintainers usually reply within 1 day
-
area: ui Bug created-by: Contributor status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
payloadcms/payload#19396 ·
Maintainers usually reply within 1 day
-
area: ui Bug created-by: Contributor status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19395 ·
Maintainers usually reply within 1 day
All issues in payloadcms/payload
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
documentation easy good first issue help wanted
Difficulty 1/5 1-3 hours Newbie friendliness 83/100
Maintainers usually reply within 1 day
-
Billion-context documentation addendum: nudge is independent of window occupancy. | billion-context 文档补充:nudge 与窗口占用无关Possibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 1/5 1-3 hours Newbie friendliness 86/100
ranxianglei/billion-context#5893 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
microsoft/monaco-editor#5575 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100