Package: @accelint/design-toolkit@9.10.0 — packages/design-toolkit/src/components/drawer/styles.module.css
Severity: Demo-blocking — produces a horizontal page scrollbar in three downstream Next.js consumer apps.
Introduced by: PR #936 ("Animation"), commit 62c5a63d, 2026-04-13.
Related (separate issue): #992 — closed-state grid column doesn't collapse. Same component, distinct bug.
TL;DR
PR #936 added placement-aware transform: translateX/Y(±100%) rules to the .drawer element. The .panel element (a child of .drawer) already had identical placement-aware translate* rules from before. PR #936 didn't notice the duplicate. Now both elements translate 100% independently when the drawer is closed, and the translates compound: the inner panel ends up 200% off-screen.
For a size='large' drawer (--drawer-size-large: 400px), this puts the panel 800px beyond the viewport edge, producing a horizontal page scrollbar of exactly 2 × --drawer-size-large when the drawer is closed.
The bug is dormant when the drawer is open (both transforms become translate(0,0)), which is why it survived release review — the Storybook open-state visuals look fine.
Repro
- App with a
<DrawerLayout push='left right'> containing a <Drawer placement='right' size='large'>.
- Render with the drawer in the closed state (default for our consumers — drawer opens on user action).
- Inspect
document.documentElement.scrollWidth. Expected: equal to viewport width. Actual: viewport + 2 × --drawer-size-large.
Direct DOM measurements from one of our apps (viewport 2323px wide):
.layout x=0, w=2323, scrollWidth=3123 ← +800px overflow
.drawer x=2323, w=400, transform=translateX(400px) (✗ added by #936)
.panel x=2723, w=400, transform=translateX(400px) (existed pre-#936)
↑
panel sits 800px past viewport edge
2 × 400 = 800 is exact, not coincidental — it's the doubled translate of --drawer-size-large.
Diff PR #936 introduced
The .drawer rule was previously a pure layout selector (grid placement only). PR #936 added the placement-aware translate variants:
.drawer {
@apply bg-surface-default text-body-m pointer-events-none relative flex flex-col;
+ @variant motion-safe {
+ transition: transform var(--animation-duration-slow)
+ var(--animation-easing-standard);
+ }
@variant placement-top {
@apply col-span-full row-start-1 row-end-2;
+ transform: translateY(-100%);
+
+ @variant open {
+ transform: translateY(0);
+ }
…
}
@variant placement-right {
@apply col-start-3 col-end-4 row-span-full;
+ transform: translateX(100%);
+
+ @variant open {
+ transform: translateX(0);
+ }
…
}
@variant placement-bottom { …same pattern… }
@variant placement-left { …same pattern… }
}
The pre-existing .panel rules already contained equivalent placement-aware translates:
.panel {
…
@variant group-placement-right/drawer { transform: translateX(100%); }
@variant group-placement-left/drawer { transform: translateX(-100%); }
@variant group-placement-top/drawer { transform: translateY(-100%); }
@variant group-placement-bottom/drawer { transform: translateY(100%); }
@variant group-open/drawer {
opacity: 1;
pointer-events: auto;
transform: translate(0, 0);
}
}
.panel is a child of .drawer. CSS transforms on a parent compose with transforms on children. With both translating 100% on close, the panel slides 100% + 100% = 200% off-screen.
Proposed fix
Remove the placement-aware transforms PR #936 added to .drawer. Keep the transition (it's still useful for any width/height/opacity animation we want to layer in). The pre-existing .panel translates already deliver the slide-out animation.
.drawer {
@apply bg-surface-default text-body-m pointer-events-none relative flex flex-col;
@variant motion-safe {
transition: transform var(--animation-duration-slow)
var(--animation-easing-standard);
}
@variant placement-top {
@apply col-span-full row-start-1 row-end-2;
- transform: translateY(-100%);
-
- @variant open {
- transform: translateY(0);
- }
…
}
@variant placement-right { …similar deletion… }
@variant placement-bottom { …similar deletion… }
@variant placement-left { …similar deletion… }
}
This is the minimum-change fix that retires the horizontal-scroll bug. It does not retire the related (pre-existing) bug filed at #992 — that's a separate issue (closed grid column doesn't collapse, contributing a footer indent). They're independent.
Caveat: overflow: hidden on .drawer clips the menu trigger
Heads-up for whoever picks this up — we tried adding overflow: hidden on the closed .drawer (as part of the proposed fix in #992 to collapse the grid column) and confirmed it retires both bugs (page horizontal scroll = 0). However, it also makes the drawer's menu trigger button disappear:
.menu {
@apply rounded-large bg-surface-default p-xs gap-xs absolute flex;
@variant group-placement-right/drawer {
@apply right-full flex-col rounded-r-none;
}
}
.menu is position: absolute inside .drawer (which is position: relative), positioned to render outside the drawer to the side via right-full. Adding overflow: hidden to .drawer clips its absolutely-positioned descendants — including the menu trigger button. Result: drawer menu is invisible/inaccessible.
Any fix involving overflow: hidden on .drawer needs to also lift the menu trigger out of the drawer, or apply the clip on a different layer (the layout, the panel, etc.). The translate-removal fix above sidesteps this by not introducing overflow at all.
Suggested validation
- Storybook story for
<Drawer placement='right' size='large'> in closed state (not just default open). Assert document.documentElement.scrollWidth === viewportWidth after render.
- Storybook story toggling open ↔ closed. Assert no horizontal scrollbar in either state and the menu trigger remains visible/clickable through the cycle.
- Repeat for all four placements (top / right / bottom / left).
- Cross-check
data-extend / push='left right' consumer wiring — that's the configuration our apps use.
Workaround we're using until upstream lands
App-side overflow-x-hidden on the root layout in each consumer fork:
- <div className='flex h-screen flex-col'>
+ <div className='flex h-screen flex-col overflow-x-hidden'>
This clips horizontal page overflow at the layout root without touching the toolkit. It does not affect the menu trigger (which is positioned inside the viewport, not in the overflow direction). Tooltips/dropdowns/dialogs are unaffected because they portal outside the layout tree.
Versions
@accelint/design-toolkit@9.10.0
- Next.js 16, React 19, Turbopack, pnpm 10 hoisted
- Three downstream consumer apps reproducing.
Happy to PR the translate-removal diff.
Package:
@accelint/design-toolkit@9.10.0—packages/design-toolkit/src/components/drawer/styles.module.cssSeverity: Demo-blocking — produces a horizontal page scrollbar in three downstream Next.js consumer apps.
Introduced by: PR #936 ("Animation"), commit
62c5a63d, 2026-04-13.Related (separate issue): #992 — closed-state grid column doesn't collapse. Same component, distinct bug.
TL;DR
PR #936 added placement-aware
transform: translateX/Y(±100%)rules to the.drawerelement. The.panelelement (a child of.drawer) already had identical placement-awaretranslate*rules from before. PR #936 didn't notice the duplicate. Now both elements translate100%independently when the drawer is closed, and the translates compound: the inner panel ends up200%off-screen.For a
size='large'drawer (--drawer-size-large: 400px), this puts the panel 800px beyond the viewport edge, producing a horizontal page scrollbar of exactly 2 ×--drawer-size-largewhen the drawer is closed.The bug is dormant when the drawer is open (both transforms become
translate(0,0)), which is why it survived release review — the Storybook open-state visuals look fine.Repro
<DrawerLayout push='left right'>containing a<Drawer placement='right' size='large'>.document.documentElement.scrollWidth. Expected: equal to viewport width. Actual: viewport +2 × --drawer-size-large.Direct DOM measurements from one of our apps (viewport 2323px wide):
2 × 400 = 800is exact, not coincidental — it's the doubled translate of--drawer-size-large.Diff PR #936 introduced
The
.drawerrule was previously a pure layout selector (grid placement only). PR #936 added the placement-aware translate variants:.drawer { @apply bg-surface-default text-body-m pointer-events-none relative flex flex-col; + @variant motion-safe { + transition: transform var(--animation-duration-slow) + var(--animation-easing-standard); + } @variant placement-top { @apply col-span-full row-start-1 row-end-2; + transform: translateY(-100%); + + @variant open { + transform: translateY(0); + } … } @variant placement-right { @apply col-start-3 col-end-4 row-span-full; + transform: translateX(100%); + + @variant open { + transform: translateX(0); + } … } @variant placement-bottom { …same pattern… } @variant placement-left { …same pattern… } }The pre-existing
.panelrules already contained equivalent placement-aware translates:.panelis a child of.drawer. CSS transforms on a parent compose with transforms on children. With both translating 100% on close, the panel slides100% + 100% = 200%off-screen.Proposed fix
Remove the placement-aware transforms PR #936 added to
.drawer. Keep thetransition(it's still useful for any width/height/opacity animation we want to layer in). The pre-existing.paneltranslates already deliver the slide-out animation..drawer { @apply bg-surface-default text-body-m pointer-events-none relative flex flex-col; @variant motion-safe { transition: transform var(--animation-duration-slow) var(--animation-easing-standard); } @variant placement-top { @apply col-span-full row-start-1 row-end-2; - transform: translateY(-100%); - - @variant open { - transform: translateY(0); - } … } @variant placement-right { …similar deletion… } @variant placement-bottom { …similar deletion… } @variant placement-left { …similar deletion… } }This is the minimum-change fix that retires the horizontal-scroll bug. It does not retire the related (pre-existing) bug filed at #992 — that's a separate issue (closed grid column doesn't collapse, contributing a footer indent). They're independent.
Caveat:
overflow: hiddenon.drawerclips the menu triggerHeads-up for whoever picks this up — we tried adding
overflow: hiddenon the closed.drawer(as part of the proposed fix in #992 to collapse the grid column) and confirmed it retires both bugs (page horizontal scroll = 0). However, it also makes the drawer's menu trigger button disappear:.menuisposition: absoluteinside.drawer(which isposition: relative), positioned to render outside the drawer to the side viaright-full. Addingoverflow: hiddento.drawerclips its absolutely-positioned descendants — including the menu trigger button. Result: drawer menu is invisible/inaccessible.Any fix involving
overflow: hiddenon.drawerneeds to also lift the menu trigger out of the drawer, or apply the clip on a different layer (the layout, the panel, etc.). The translate-removal fix above sidesteps this by not introducing overflow at all.Suggested validation
<Drawer placement='right' size='large'>in closed state (not just default open). Assertdocument.documentElement.scrollWidth === viewportWidthafter render.data-extend/push='left right'consumer wiring — that's the configuration our apps use.Workaround we're using until upstream lands
App-side
overflow-x-hiddenon the root layout in each consumer fork:This clips horizontal page overflow at the layout root without touching the toolkit. It does not affect the menu trigger (which is positioned inside the viewport, not in the overflow direction). Tooltips/dropdowns/dialogs are unaffected because they portal outside the layout tree.
Versions
@accelint/design-toolkit@9.10.0Happy to PR the translate-removal diff.