Skip to content

✨ feat: expose Sidebar drawer slot classNames and raise root z-index#631

Merged
futjesus merged 1 commit into
mainfrom
feat/sidebar-drawer-slot-classnames
Apr 28, 2026
Merged

✨ feat: expose Sidebar drawer slot classNames and raise root z-index#631
futjesus merged 1 commit into
mainfrom
feat/sidebar-drawer-slot-classnames

Conversation

@futjesus
Copy link
Copy Markdown
Member

Summary

Two related Sidebar fixes for drawer mode:

  1. Drawer rendered behind sticky app bars. The Drawer wraps its overlay and panel inside a fixed inset-0 z-40 root that creates a stacking context. Even with z-[51] on the panel, the whole drawer stays trapped at z-40 from the outside, so a host app with a sticky z-50 header (a very common pattern) painted on top of the open drawer instead of being covered by it. The Sidebar Wrapper now passes classNames.root: 'z-[60]' to the Drawer in drawer mode so the stacking-context root itself wins over standard sticky headers and most floating triggers without consumers having to fight twmerge through the panel className.

  2. No way to style individual drawer slots. The Sidebar previously only allowed customizing the panel via wrapperClassName (and even that was applied to the panel, not the root). Apps can now pass a drawerClassNames prop matching Drawer's full classNames shape — root, overlay, panel, closeButton, content, header, body, footer, resizeHandle.

Implementation

  • Sidebar.types.ts — adds drawerClassNames?: DrawerClassNames (imported from @/components/Drawer/Drawer.types).
  • Wrapper.tsx — destructures drawerClassNames, spreads it onto the Drawer's classNames and merges via cn against the internal defaults so user classes win on conflict:
    • root: default z-[60] + user's root.
    • panel: wrapperSiderbarVariants({ mode: 'expanded' }) + 'h-full border-r-0' + wrapperClassName + user's panel.
    • content: default 'gap-0' + user's content.
    • All other slots (closeButton, overlay, header, body, footer, resizeHandle) are forwarded as-is via spread.

Use cases

<Sidebar
  drawerClassNames={{
    // Raise above app-level toasts/modals at z-[80]:
    root: 'z-[100]',

    // Vertically center the close button with a 68px logo box:
    closeButton: 'top-1/2 -translate-y-1/2',

    // Let dividers under the logo / between groups span edge-to-edge:
    panel: 'px-0',
  }}
>
  ...
</Sidebar>

Backwards compatibility

No breaking changes. New prop is optional. Default z-[60] for the drawer root is the only behavior change — apps that depended on the previous z-40 and had higher-z elements they wanted to keep on top can override via drawerClassNames={{ root: 'z-40' }} to opt back in.

Tests

Existing Sidebar.test.tsx covers drawer open/close/auto-close paths. Full suite: 429 / 429 passing locally.

Test plan

  • CI lint, typecheck, prettier, and unit tests pass on the PR branch.
  • Manual smoke in a consumer app with a z-50 sticky AppBar: drawer panel covers the AppBar when open (previously hidden behind it).
  • Verify per-slot overrides:
    • drawerClassNames={{ closeButton: 'top-1/2 -translate-y-1/2' }} re-positions the X button.
    • drawerClassNames={{ panel: 'px-0' }} lets a child border-b span the full panel width.
    • drawerClassNames={{ root: 'z-[100]' }} raises the drawer above other high-z layers.

Open drawers were rendering behind sticky/fixed app bars because the
Drawer's root wrapper at `z-40` creates a stacking context that traps
its panel — even with `z-[51]` on the panel, the drawer as a whole stays
below a `z-50` header from the outside. The Sidebar wrapper now passes
`classNames.root: 'z-[60]'` to the Drawer in `drawer` mode so the
stacking context itself wins over standard sticky headers and most
floating triggers without consumers having to fight twmerge through the
panel className.

Adds a `drawerClassNames` prop on the Sidebar (mirroring `Drawer`'s
`classNames` shape: `root`, `overlay`, `panel`, `closeButton`, `content`,
`header`, `body`, `footer`, `resizeHandle`) so apps can:

- Raise the root z-index further (`drawerClassNames={{ root: 'z-[100]' }}`)
  when the host has higher-z layers it must still cover.
- Re-position the close button (`drawerClassNames={{ closeButton: 'top-1/2 -translate-y-1/2' }}`)
  to align with a custom header layout.
- Drop the panel's horizontal padding (`drawerClassNames={{ panel: 'px-0' }}`)
  so dividers below the logo or between groups can span edge-to-edge.

User-provided classes are merged with internal defaults via `cn`, so they
win on conflicts thanks to twmerge.
@futjesus futjesus merged commit d6551a8 into main Apr 28, 2026
1 check passed
@futjesus futjesus deleted the feat/sidebar-drawer-slot-classnames branch April 28, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant