moduix

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.

Base UI API

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.

PropertyDefaultDescription
--drawer-backdrop-bgvar(--backdrop-bg, var(--color-overlay))Backdrop background.
--drawer-backdrop-pointer-eventsautoBackdrop pointer-events behavior.
--drawer-backdrop-transition450ms cubic-bezier(0.32, 0.72, 0, 1)Backdrop transition.
--drawer-bgvar(--color-popover)Popup background color.
--drawer-body-font-sizevar(--text-md)Body font size.
--drawer-body-line-heightvar(--line-height-text-md)Body line height.
--drawer-body-margin-topvar(--spacing-4)Spacing above body content.
--drawer-bleed-size3remOff-screen bleed size for edge drawers.
--drawer-border-colorvar(--color-border)Popup border color.
--drawer-colorvar(--color-popover-foreground)Popup text color.
--drawer-control-bgvar(--color-background)Control background color.
--drawer-control-bg-hovervar(--color-accent)Control hover background color.
--drawer-control-border-colorvar(--color-border)Control border color.
--drawer-control-border-widthvar(--border-width-sm)Control border width.
--drawer-control-colorvar(--color-foreground)Control text color.
--drawer-control-font-sizevar(--text-md)Control font size.
--drawer-control-heightvar(--size-lg)Control minimum height.
--drawer-control-line-heightvar(--line-height-text-md)Control line height.
--drawer-control-padding-x0.875remControl horizontal padding.
--drawer-control-padding-y0.5remControl vertical padding.
--drawer-control-radiusvar(--radius-md)Control border radius.
--drawer-description-colorvar(--color-muted-foreground)Description text color.
--drawer-description-font-sizevar(--text-md)Description font size.
--drawer-description-line-heightvar(--line-height-text-md)Description line height.
--drawer-focus-ring-colorvar(--color-ring)Focus ring color for interactive controls.
--drawer-focus-ring-widthvar(--drawer-control-border-width)Focus ring width.
--drawer-footer-gapvar(--spacing-2)Spacing between footer actions.
--drawer-footer-margin-topvar(--spacing-6)Spacing above footer.
--drawer-handle-bgvar(--color-muted-foreground)Handle color.
--drawer-handle-height0.25remHandle height.
--drawer-handle-offsetvar(--spacing-3)Handle offset from edge.
--drawer-handle-opacity0.45Handle opacity.
--drawer-handle-radiusvar(--radius-full)Handle border radius.
--drawer-handle-width3remHandle width.
--drawer-header-gapvar(--spacing-1)Gap between header elements.
--drawer-indent-background-bgvar(--color-foreground)Indent background color.
--drawer-indent-background-opacity0Indent background opacity in idle state.
--drawer-indent-background-opacity-active1Indent background opacity in active state.
--drawer-indent-radius-activevar(--radius-lg)Active indent radius.
--drawer-indent-radius-transition250ms cubic-bezier(0.32, 0.72, 0, 1)Indent radius transition.
--drawer-indent-scale-active0.98Active indent scale.
--drawer-indent-transition400ms cubic-bezier(0.32, 0.72, 0, 1)Indent transition.
--drawer-indent-translate-y-activevar(--spacing-2)Active indent Y translation.
--drawer-island-insetvar(--spacing-2)Inset for island variant.
--drawer-max-height80vhMaximum height for top and bottom drawers.
--drawer-nested-peek2.75remVisible peek of nested drawers.
--drawer-nested-scale-step0.06Scale step for nested drawers.
--drawer-padding-xvar(--spacing-6)Popup horizontal padding.
--drawer-padding-yvar(--spacing-4)Popup vertical padding.
--drawer-popup-pointer-eventsautoPopup pointer-events behavior.
--drawer-radiusvar(--radius-xl)Popup border radius.
--drawer-shadowvar(--shadow-lg)Popup shadow.
--drawer-side-height100%Height of left and right drawers.
--drawer-side-max-height100%Maximum height of left and right drawers.
--drawer-side-width22remWidth of left and right drawers.
--drawer-snap-toggle-bgtransparentSnap toggle background color.
--drawer-snap-toggle-bg-hovervar(--color-accent)Snap toggle hover background color.
--drawer-snap-toggle-border-colorcurrentColorSnap toggle border color.
--drawer-snap-toggle-border-stylesolidSnap toggle border style.
--drawer-snap-toggle-border-width0Snap toggle border width.
--drawer-snap-toggle-colorvar(--drawer-description-color, var(--color-muted-foreground))Snap toggle icon color.
--drawer-snap-toggle-icon-size1remSnap toggle icon size.
--drawer-snap-toggle-radiusvar(--radius-md)Snap toggle border radius.
--drawer-snap-toggle-size1.75remSnap toggle button size.
--drawer-swipe-area-sizevar(--spacing-10)Edge swipe area size.
--drawer-title-colorvar(--drawer-color)Title text color.
--drawer-title-font-sizevar(--text-lg)Title font size.
--drawer-title-font-weightvar(--weight-semibold)Title font weight.
--drawer-title-line-heightvar(--line-height-text-lg)Title line height.
--drawer-transition450ms cubic-bezier(0.32, 0.72, 0, 1)Popup transition.
--drawer-viewport-bottom0Viewport bottom inset.
--drawer-viewport-left0Viewport left inset.
--drawer-viewport-padding0pxViewport padding (island variant defaults to var(--drawer-island-inset)).
--drawer-viewport-pointer-eventsautoViewport pointer-events behavior.
--drawer-viewport-right0Viewport right inset.
--drawer-viewport-top0Viewport top inset.
--drawer-width100%Width of top and bottom drawers.

Interactive variables scoped for docs preview without changing size scale tokens.

PropertyValueDefaultDescription
--drawer-backdrop-bgvar(--backdrop-bg, var(--color-overlay))Controls backdrop.
--drawer-bgvar(--color-popover)Controls popup background.
--drawer-border-colorvar(--color-border)Controls popup border color.
--drawer-colorvar(--color-popover-foreground)Controls popup text color.
--drawer-handle-bgvar(--color-muted-foreground)Controls handle color.
--drawer-max-height80vhControls popup max height.
--drawer-padding-xvar(--spacing-6)Controls popup horizontal padding.
--drawer-padding-yvar(--spacing-4)Controls popup vertical padding.
--drawer-radiusvar(--radius-xl)Controls popup border radius.
--drawer-shadowvar(--shadow-lg)Controls popup shadow.
--drawer-side-width22remControls side drawer width.
--drawer-width100%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>
PartRole
DrawerRoot state and gesture model. Use open, defaultOpen, onOpenChange, modal, and snap props.
DrawerTriggerOpens the drawer from user interaction. Use handle when trigger logic is managed outside the tree.
DrawerContentPopup container. Renders internal portal, backdrop, viewport, handle, and content slots.
DrawerHeaderGroups title-level content at the top of the panel.
DrawerTitleAccessible drawer title announced by assistive technology.
DrawerDescriptionOptional supporting text announced with the title.
DrawerBodyOptional content region for forms, lists, and long scrollable content.
DrawerFooterOptional actions row for close, submit, and secondary actions.
DrawerCloseCloses 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;}

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);}

On this page