Drawer
A panel that slides in from the edge of the screen with swipe gestures and snap points.
API Reference
Original primitive API
Behavior, accessibility details, and low-level props are documented by Base UI.
When to use Drawer
Use Drawer as the default choice for edge-attached panels in moduix.
It covers both patterns that are often split in other libraries:
- Drawer behavior: edge placement, swipe-to-dismiss, optional edge swipe area, and snap points.
- Sheet behavior: structured panel content (header/body/footer), side and bottom layouts, and non-modal/persistent configurations.
If a panel does not need gestures or snap points, prefer Dialog with edge positioning.
Basic
import { Button, Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function DrawerDemo() { return ( <Drawer> <DrawerTrigger render={<Button />}>Open bottom drawer</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Notifications</DrawerTitle> <DrawerDescription>You are all caught up. Good job!</DrawerDescription> </DrawerHeader> <DrawerBody>Bottom drawers are the default.</DrawerBody> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --drawer-backdrop-bg | var(--backdrop-bg, var(--color-overlay)) | Backdrop background. |
| --drawer-backdrop-pointer-events | auto | Backdrop pointer-events behavior. |
| --drawer-backdrop-transition | 450ms cubic-bezier(0.32, 0.72, 0, 1) | Backdrop transition. |
| --drawer-bg | var(--color-popover) | Popup background color. |
| --drawer-body-font-size | var(--text-md) | Body font size. |
| --drawer-body-line-height | var(--line-height-text-md) | Body line height. |
| --drawer-body-margin-top | var(--spacing-4) | Spacing above body content. |
| --drawer-bleed-size | 3rem | Off-screen bleed size for edge drawers. |
| --drawer-border-color | var(--color-border) | Popup border color. |
| --drawer-color | var(--color-popover-foreground) | Popup text color. |
| --drawer-control-bg | var(--color-background) | Control background color. |
| --drawer-control-bg-hover | var(--color-accent) | Control hover background color. |
| --drawer-control-border-color | var(--color-border) | Control border color. |
| --drawer-control-border-width | var(--border-width-sm) | Control border width. |
| --drawer-control-color | var(--color-foreground) | Control text color. |
| --drawer-control-font-size | var(--text-md) | Control font size. |
| --drawer-control-height | var(--size-lg) | Control minimum height. |
| --drawer-control-line-height | var(--line-height-text-md) | Control line height. |
| --drawer-control-padding-x | 0.875rem | Control horizontal padding. |
| --drawer-control-padding-y | 0.5rem | Control vertical padding. |
| --drawer-control-radius | var(--radius-md) | Control border radius. |
| --drawer-description-color | var(--color-muted-foreground) | Description text color. |
| --drawer-description-font-size | var(--text-md) | Description font size. |
| --drawer-description-line-height | var(--line-height-text-md) | Description line height. |
| --drawer-focus-ring-color | var(--color-ring) | Focus ring color for interactive controls. |
| --drawer-focus-ring-width | var(--drawer-control-border-width) | Focus ring width. |
| --drawer-footer-gap | var(--spacing-2) | Spacing between footer actions. |
| --drawer-footer-margin-top | var(--spacing-6) | Spacing above footer. |
| --drawer-handle-bg | var(--color-muted-foreground) | Handle color. |
| --drawer-handle-height | 0.25rem | Handle height. |
| --drawer-handle-offset | var(--spacing-3) | Handle offset from edge. |
| --drawer-handle-opacity | 0.45 | Handle opacity. |
| --drawer-handle-radius | var(--radius-full) | Handle border radius. |
| --drawer-handle-width | 3rem | Handle width. |
| --drawer-header-gap | var(--spacing-1) | Gap between header elements. |
| --drawer-indent-background-bg | var(--color-foreground) | Indent background color. |
| --drawer-indent-background-opacity | 0 | Indent background opacity in idle state. |
| --drawer-indent-background-opacity-active | 1 | Indent background opacity in active state. |
| --drawer-indent-radius-active | var(--radius-lg) | Active indent radius. |
| --drawer-indent-radius-transition | 250ms cubic-bezier(0.32, 0.72, 0, 1) | Indent radius transition. |
| --drawer-indent-scale-active | 0.98 | Active indent scale. |
| --drawer-indent-transition | 400ms cubic-bezier(0.32, 0.72, 0, 1) | Indent transition. |
| --drawer-indent-translate-y-active | var(--spacing-2) | Active indent Y translation. |
| --drawer-island-inset | var(--spacing-2) | Inset for island variant. |
| --drawer-max-height | 80vh | Maximum height for top and bottom drawers. |
| --drawer-nested-peek | 2.75rem | Visible peek of nested drawers. |
| --drawer-nested-scale-step | 0.06 | Scale step for nested drawers. |
| --drawer-padding-x | var(--spacing-6) | Popup horizontal padding. |
| --drawer-padding-y | var(--spacing-4) | Popup vertical padding. |
| --drawer-popup-pointer-events | auto | Popup pointer-events behavior. |
| --drawer-radius | var(--radius-xl) | Popup border radius. |
| --drawer-shadow | var(--shadow-lg) | Popup shadow. |
| --drawer-side-height | 100% | Height of left and right drawers. |
| --drawer-side-max-height | 100% | Maximum height of left and right drawers. |
| --drawer-side-width | 22rem | Width of left and right drawers. |
| --drawer-snap-toggle-bg | transparent | Snap toggle background color. |
| --drawer-snap-toggle-bg-hover | var(--color-accent) | Snap toggle hover background color. |
| --drawer-snap-toggle-border-color | currentColor | Snap toggle border color. |
| --drawer-snap-toggle-border-style | solid | Snap toggle border style. |
| --drawer-snap-toggle-border-width | 0 | Snap toggle border width. |
| --drawer-snap-toggle-color | var(--drawer-description-color, var(--color-muted-foreground)) | Snap toggle icon color. |
| --drawer-snap-toggle-icon-size | 1rem | Snap toggle icon size. |
| --drawer-snap-toggle-radius | var(--radius-md) | Snap toggle border radius. |
| --drawer-snap-toggle-size | 1.75rem | Snap toggle button size. |
| --drawer-swipe-area-size | var(--spacing-10) | Edge swipe area size. |
| --drawer-title-color | var(--drawer-color) | Title text color. |
| --drawer-title-font-size | var(--text-lg) | Title font size. |
| --drawer-title-font-weight | var(--weight-semibold) | Title font weight. |
| --drawer-title-line-height | var(--line-height-text-lg) | Title line height. |
| --drawer-transition | 450ms cubic-bezier(0.32, 0.72, 0, 1) | Popup transition. |
| --drawer-viewport-bottom | 0 | Viewport bottom inset. |
| --drawer-viewport-left | 0 | Viewport left inset. |
| --drawer-viewport-padding | 0px | Viewport padding (island variant defaults to var(--drawer-island-inset)). |
| --drawer-viewport-pointer-events | auto | Viewport pointer-events behavior. |
| --drawer-viewport-right | 0 | Viewport right inset. |
| --drawer-viewport-top | 0 | Viewport top inset. |
| --drawer-width | 100% | Width of top and bottom drawers. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --drawer-backdrop-bg | var(--backdrop-bg, var(--color-overlay)) | Controls backdrop. | |
| --drawer-bg | var(--color-popover) | Controls popup background. | |
| --drawer-border-color | var(--color-border) | Controls popup border color. | |
| --drawer-color | var(--color-popover-foreground) | Controls popup text color. | |
| --drawer-handle-bg | var(--color-muted-foreground) | Controls handle color. | |
| --drawer-max-height | 80vh | Controls popup max height. | |
| --drawer-padding-x | var(--spacing-6) | Controls popup horizontal padding. | |
| --drawer-padding-y | var(--spacing-4) | Controls popup vertical padding. | |
| --drawer-radius | var(--radius-xl) | Controls popup border radius. | |
| --drawer-shadow | var(--shadow-lg) | Controls popup shadow. | |
| --drawer-side-width | 22rem | Controls side drawer width. | |
| --drawer-width | 100% | Controls top and bottom drawer width. |
Anatomy
Drawer combines root state/gesture behavior with one visible panel and internal service slots.
Use DrawerContent as the composed popup container. It renders portal, backdrop, viewport,
handle, and the inner content slot for you.
Drawer
├─ DrawerTrigger (optional)
└─ DrawerContent
├─ portal (service slot)
│ ├─ backdrop (service slot, optional)
│ └─ viewport (service slot)
│ └─ popup surface
│ ├─ handle (service slot, optional)
│ └─ content (service slot)
│ ├─ DrawerHeader
│ │ ├─ DrawerTitle
│ │ └─ DrawerDescription (optional)
│ ├─ DrawerBody (optional)
│ └─ DrawerFooter (optional)
│ └─ DrawerClose<Drawer>
<DrawerTrigger render={<Button />}>Open drawer</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Notifications</DrawerTitle>
<DrawerDescription>You are all caught up. Good job!</DrawerDescription>
</DrawerHeader>
<DrawerBody>Bottom drawers are the default.</DrawerBody>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>| Part | Role |
|---|---|
Drawer | Root state and gesture model. Use open, defaultOpen, onOpenChange, modal, and snap props. |
DrawerTrigger | Opens the drawer from user interaction. Use handle when trigger logic is managed outside the tree. |
DrawerContent | Popup container. Renders internal portal, backdrop, viewport, handle, and content slots. |
DrawerHeader | Groups title-level content at the top of the panel. |
DrawerTitle | Accessible drawer title announced by assistive technology. |
DrawerDescription | Optional supporting text announced with the title. |
DrawerBody | Optional content region for forms, lists, and long scrollable content. |
DrawerFooter | Optional actions row for close, submit, and secondary actions. |
DrawerClose | Closes the drawer from action buttons while preserving built-in behavior. |
In most cases, keep default layering and gestures and style visible panel parts with className
(DrawerContent, DrawerHeader, DrawerBody, DrawerFooter). Customize internal service slots with
classNames (portal, backdrop, viewport, handle, content) only when you need special
positioning, overlay, or handle behavior.
Composition
Use Drawer for root state and behavior props such as open, defaultOpen, onOpenChange,
modal, swipeDirection, snapPoints, snapPoint, and onSnapPointChange. Use DrawerContent
for the default composed surface. Its className and regular popup props are passed to
the visible sliding panel.
Use withBackdrop, withHandle, variant, snapLayout, and container for common behavior.
className styles the visible popup. classNames styles the internal slots hidden from the default
composition. Use slotProps only when those internal slots need non-class props from Base UI:
<DrawerContent
className={styles.popup}
classNames={{
portal: styles.portal,
backdrop: styles.backdrop,
viewport: styles.viewport,
handle: styles.handle,
content: styles.content,
}}
slotProps={{
portal: { keepMounted: true },
backdrop: { forceRender: true },
viewport: { render: <div /> },
handle: { 'aria-hidden': true },
}}
/>Examples
Top
Use swipeDirection="up" for a drawer attached to the top edge.
import { Button, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function TopDrawerDemo() { return ( <Drawer swipeDirection="up"> <DrawerTrigger render={<Button />}>Open top drawer</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Top panel</DrawerTitle> <DrawerDescription>Set swipeDirection to up for a top drawer.</DrawerDescription> </DrawerHeader> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}Left
Use swipeDirection="left" for left-side navigation, filters, or mobile panels.
import { Button, Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function LeftDrawerDemo() { return ( <Drawer swipeDirection="left"> <DrawerTrigger render={<Button />}>Open left drawer</DrawerTrigger> <DrawerContent className={styles.sideContent}> <DrawerHeader> <DrawerTitle>Filters</DrawerTitle> <DrawerDescription>Side drawers use the same composition.</DrawerDescription> </DrawerHeader> <DrawerBody>Use side drawers for filters, navigation, or contextual panels.</DrawerBody> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}.sideContent { --drawer-side-width: 26rem;}Right
Use swipeDirection="right" for inspectors, details, and secondary workflows.
Side drawers are full-height by default. Set --drawer-side-height to a fixed length,
percentage, auto, or max-content when a side drawer should be shorter or fit its content.
Use --drawer-side-max-height to cap long content and keep the panel inside the viewport.
import { Button, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function RightDrawerDemo() { return ( <Drawer swipeDirection="right"> <DrawerTrigger render={<Button />}>Open right drawer</DrawerTrigger> <DrawerContent className={styles.sideContent}> <DrawerHeader> <DrawerTitle>Details</DrawerTitle> <DrawerDescription>Set width through CSS variables or className.</DrawerDescription> </DrawerHeader> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}.sideContent { --drawer-side-width: 26rem;}Snap Points
Pass snapPoints, snapPoint, and onSnapPointChange when the drawer should settle at preset
heights. snapPoints is the list of allowed positions. snapPoint is the currently active
position when controlled. onSnapPointChange receives the next active position. Set snapLayout
on DrawerContent when the content should visually resize with the active snap point. It does not
enable snapping by itself; it applies the library's snap-aware layout styles for the popup tail,
drag offset, and inner content height.
import { Button, Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, ScrollArea, DrawerTrigger,} from "moduix";import { useState } from "react";const releaseSections = [ { title: "Migration", body: "Verify migration scripts, rollback steps, and staging smoke tests.", }, { title: "Monitoring", body: "Check dashboards, alerts, and post-release health checks.", }, { title: "Rollout", body: "Confirm feature flags, analytics events, and support notes.", },];export function SnapPointsDrawerDemo() { const snapPoints = [0.35, 0.65, 1]; const [snapPoint, setSnapPoint] = useState(snapPoints[1] as number | string | null); return ( <Drawer snapPoints={snapPoints} snapPoint={snapPoint} onSnapPointChange={setSnapPoint}> <DrawerTrigger render={<Button />}>Open snap drawer</DrawerTrigger> <DrawerContent snapLayout> <DrawerHeader> <DrawerTitle>Release checklist</DrawerTitle> <DrawerDescription>Current snap point: {String(snapPoint)}</DrawerDescription> </DrawerHeader> <DrawerBody> <ScrollArea className={styles.scrollArea} classNames={{ content: styles.scrollContent }} > {releaseSections.map((item) => ( <section key={item.title}> <h3>{item.title}</h3> <p>{item.body}</p> </section> ))} </ScrollArea> </DrawerBody> </DrawerContent> </Drawer> );}.scrollArea { height: 100%; min-height: 0;}.scrollContent { display: grid; gap: var(--spacing-4); padding-right: var(--spacing-5);}Without Backdrop
Set withBackdrop={false} when the drawer should not render an overlay.
import { Button, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function WithoutBackdropDrawerDemo() { return ( <Drawer> <DrawerTrigger render={<Button />}>Open without backdrop</DrawerTrigger> <DrawerContent withBackdrop={false}> <DrawerHeader> <DrawerTitle>No backdrop</DrawerTitle> <DrawerDescription>Use this when the page should stay visually available.</DrawerDescription> </DrawerHeader> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}Nested
Drawers can be nested. Base UI exposes nested-drawer state attributes and CSS variables to style the stack.
import { Button, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function NestedDrawerDemo() { return ( <Drawer> <DrawerTrigger render={<Button />}>Open drawer stack</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Account</DrawerTitle> <DrawerDescription>Nested drawers visually recede while the child is active.</DrawerDescription> </DrawerHeader> <DrawerFooter> <div className={styles.nestedActionsStart}> <Drawer> <DrawerTrigger render={<Button />}>Open nested</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Nested drawer</DrawerTitle> <DrawerDescription>Second layer in the stack.</DrawerDescription> </DrawerHeader> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close nested</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> </div> <DrawerClose render={<Button variant="outline" />}>Close root</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}.nestedActionsStart { margin-right: auto;}Bottom Island
Use variant="island" to render the popup inset from the viewport instead of bleeding off-screen.
import { Button, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function BottomIslandDrawerDemo() { return ( <Drawer> <DrawerTrigger render={<Button />}>Open bottom island</DrawerTrigger> <DrawerContent variant="island" className={styles.islandContent}> <DrawerHeader> <DrawerTitle>Bottom island</DrawerTitle> <DrawerDescription>Island drawers remove the bleed tail.</DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> );}.islandContent { --drawer-width: 32rem; --drawer-side-width: 26rem;}Top Island
The island variant works with top drawers too.
import { Button, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function TopIslandDrawerDemo() { return ( <Drawer swipeDirection="up"> <DrawerTrigger render={<Button />}>Open top island</DrawerTrigger> <DrawerContent variant="island" className={styles.islandContent}> <DrawerHeader> <DrawerTitle>Top island</DrawerTitle> <DrawerDescription>The same variant works from the top edge.</DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> );}.islandContent { --drawer-width: 32rem; --drawer-side-width: 26rem;}Left Island
Use side island drawers when the panel should not touch the viewport edge.
import { Button, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function LeftIslandDrawerDemo() { return ( <Drawer swipeDirection="left"> <DrawerTrigger render={<Button />}>Open left island</DrawerTrigger> <DrawerContent variant="island" className={styles.islandContent}> <DrawerHeader> <DrawerTitle>Left island</DrawerTitle> <DrawerDescription>Side island drawers keep an inset around the viewport.</DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> );}.islandContent { --drawer-width: 32rem; --drawer-side-width: 26rem;}Right Island
Customize important slots with className, CSS Modules, Tailwind, or CSS-in-JS.
import { Button, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function RightIslandDrawerDemo() { return ( <Drawer swipeDirection="right"> <DrawerTrigger render={<Button />}>Open right island</DrawerTrigger> <DrawerContent variant="island" className={styles.islandContent}> <DrawerHeader> <DrawerTitle>Right island</DrawerTitle> <DrawerDescription>Use className to tune important slots for your layout.</DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> );}.islandContent { --drawer-width: 32rem; --drawer-side-width: 26rem;}Persistent Snap
Combine persistent, non-modal behavior, and snap points for persistent bottom panels.
The persistent prop prevents drawer-initiated close attempts and ignores null snap points.
Place ScrollArea inside DrawerBody when the header should remain visible while only the body
content scrolls.
import { Button, Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerSnapToggle, DrawerDescription, DrawerBody, ScrollArea,} from "moduix";import { useState, Fragment } from "react";const releaseSections = [ { title: "Migration", body: "Verify migration scripts, rollback steps, and staging smoke tests.", }, { title: "Monitoring", body: "Check dashboards, alerts, and post-release health checks.", }, { title: "Rollout", body: "Confirm feature flags, analytics events, and support notes.", },];export function PersistentSnapDrawerDemo() { const snapPoints = [0.35, 0.85] as const; const [open, setOpen] = useState(false); const [snapPoint, setSnapPoint] = useState(snapPoints[0] as number | string | null); const expanded = snapPoint === snapPoints[1]; return ( <Fragment> <Button type="button" onClick={() => setOpen(true)}> Open persistent drawer </Button> <Drawer open={open} persistent modal={false} disablePointerDismissal snapPoints={[...snapPoints]} defaultSnapPoint={snapPoints[0]} snapPoint={snapPoint} onSnapPointChange={setSnapPoint} > <DrawerContent snapLayout withBackdrop={false} disableInitialAnimation> <DrawerHeader> <DrawerTitle>Persistent drawer</DrawerTitle> <DrawerSnapToggle expanded={expanded} onClick={() => setSnapPoint(expanded ? snapPoints[0] : snapPoints[1])} /> <DrawerDescription>Switch between compact and expanded states.</DrawerDescription> </DrawerHeader> <DrawerBody> <ScrollArea className={styles.scrollArea} classNames={{ content: styles.scrollContent }} > {releaseSections.map((item) => ( <section key={item.title}> <h3>{item.title}</h3> <p>{item.body}</p> </section> ))} </ScrollArea> </DrawerBody> </DrawerContent> </Drawer> </Fragment> );}.scrollArea { height: 100%; min-height: 0;}.scrollContent { display: grid; gap: var(--spacing-4); padding-right: var(--spacing-5);}Swipe Area
Place DrawerSwipeArea near a viewport edge to enable swipe-to-open gestures.
import { Button, Drawer, DrawerSwipeArea, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from "moduix";export function SwipeAreaDrawerDemo() { return ( <Drawer swipeDirection="right" modal={false}> <DrawerSwipeArea /> <DrawerTrigger render={<Button />}>Open with trigger</DrawerTrigger> <DrawerContent withBackdrop={false}> <DrawerHeader> <DrawerTitle>Swipe area</DrawerTitle> <DrawerDescription>Swipe from the left edge or use the trigger.</DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> );}Indent Effect
Wrap the surface with DrawerProvider, DrawerIndentBackground, and DrawerIndent to scale the
page while a drawer is open.
import { Button, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerProvider, DrawerIndentBackground, DrawerIndent, DrawerTitle, DrawerTrigger,} from "moduix";export function IndentEffectDrawerDemo() { return ( <DrawerProvider> <div className={styles.indentStage}> <DrawerIndentBackground /> <DrawerIndent className={styles.indentSurface}> <Drawer modal={false}> <DrawerTrigger render={<Button />}>Open indented drawer</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Indent effect</DrawerTitle> <DrawerDescription> Provider, indent, and background parts react to open drawers. </DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> </DrawerIndent> </div> </DrawerProvider> );}.indentStage { position: relative; width: min(42rem, 100%); min-height: 360px; overflow: hidden; border: var(--border-width-sm) solid var(--color-border); border-radius: var(--radius-lg); background-color: var(--color-muted);}.indentSurface { min-height: 360px; padding: var(--spacing-6); background-color: var(--color-background);}Custom Styles
Use className for the visible popup and classNames for the internal slots rendered by
DrawerContent. Pass children to DrawerSnapToggle to replace the default icon.
import { Drawer, DrawerTrigger, Button, DrawerContent, DrawerHeader, DrawerTitle, DrawerSnapToggle, DrawerFooter, DrawerClose, ChevronDownIcon, ChevronUpIcon, DrawerDescription,} from "moduix";import { useState } from "react";export function CustomStylesDrawerDemo() { const snapPoints = [0.35, 0.75] as const; const [snapPoint, setSnapPoint] = useState<number | string | null>(snapPoints[0]); const expanded = snapPoint === snapPoints[1]; return ( <Drawer snapPoints={[...snapPoints]} snapPoint={snapPoint} onSnapPointChange={setSnapPoint}> <DrawerTrigger render={<Button />}>Open custom drawer</DrawerTrigger> <DrawerContent snapLayout className={styles.customPopup} classNames={{ backdrop: styles.customBackdrop, viewport: styles.customViewport, handle: styles.customHandle, content: styles.customContent, }} > <DrawerHeader> <DrawerTitle>Custom styles</DrawerTitle> <DrawerSnapToggle expanded={expanded} onClick={() => setSnapPoint(expanded ? snapPoints[0] : snapPoints[1])} > {expanded ? <ChevronDownIcon /> : <ChevronUpIcon />} </DrawerSnapToggle> <DrawerDescription> Popup styles use className. Internal slots use classNames. </DrawerDescription> </DrawerHeader> <DrawerFooter> <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> );}.customPopup { --drawer-bg: var(--color-card); --drawer-border-color: var(--color-ring); --drawer-shadow: var(--shadow-xl); --drawer-max-height: 30rem;}.customBackdrop { --drawer-backdrop-bg: color-mix(in oklab, var(--color-foreground) 60%, transparent);}.customViewport { --drawer-viewport-padding: var(--spacing-4);}.customHandle { --drawer-handle-bg: var(--color-destructive); --drawer-handle-opacity: 0.8;}.customContent { gap: var(--spacing-4);}