Browse components

Drawer

import Drawer from "@shpaw415/mui-lite/Drawer";

Why use it

Drawer slides a panel from a screen edge for navigation, filters, or sheets.

VariantBehavior
temporaryModal overlay + backdrop; portaled above the page (default)
persistentIn-flow panel that can open/close; no backdrop
permanentAlways visible navigation rail

Demos

Temporary — anchors
const [open, setOpen] = useState(false);

<Button onClick={() => setOpen(true)}>Open</Button>
<Drawer open={open} onClose={() => setOpen(false)} anchor="left" width={280}>
  <nav>…</nav>
</Drawer>
Persistent (in-layout)

Persistent drawers push content and stay in the document flow (no backdrop, no portal).

const [open, setOpen] = useState(true);

<div style={{ display: "flex", minHeight: 200 }}>
  <Drawer variant="persistent" open={open} width={200} anchor="left">
    <nav>…</nav>
  </Drawer>
  <main style={{ flex: 1, padding: 16 }}>
    <Button onClick={() => setOpen((o) => !o)}>Toggle</Button>
  </main>
</div>
Permanent

Permanent drawers are always open in the layout.

<div style={{ display: "flex" }}>
  <Drawer variant="permanent" open width={200}>
    <nav>Always visible</nav>
  </Drawer>
  <main style={{ flex: 1 }}>Content</main>
</div>

Temporary (modal)

import { useState } from "react";
import Drawer from "@shpaw415/mui-lite/Drawer";
import Button from "@shpaw415/mui-lite/Button";
import List, { ListItem, ListItemButton, ListItemText } from "@shpaw415/mui-lite/List";
 
export function AppDrawer() {
  const [open, setOpen] = useState(false);
 
  return (
    <>
      <Button variant="outlined" onClick={() => setOpen(true)}>
        Menu
      </Button>
 
      <Drawer
        open={open}
        onClose={() => setOpen(false)}
        anchor="left"
        width={280}
      >
        <List>
          <ListItem disablePadding>
            <ListItemButton onClick={() => setOpen(false)}>
              <ListItemText primary="Home" />
            </ListItemButton>
          </ListItem>
          <ListItem disablePadding>
            <ListItemButton onClick={() => setOpen(false)}>
              <ListItemText primary="Settings" />
            </ListItemButton>
          </ListItem>
        </List>
      </Drawer>
    </>
  );
}

Anchors

<Drawer open={open} onClose={close} anchor="left" width={280}>…</Drawer>
<Drawer open={open} onClose={close} anchor="right" width={320}>…</Drawer>
<Drawer open={open} onClose={close} anchor="top">…</Drawer>
<Drawer open={open} onClose={close} anchor="bottom">…</Drawer>

Temporary drawers render in a portal (theme wrapper / document.body) so they are not clipped by overflow: hidden parents.

Persistent layout

const [open, setOpen] = useState(true);
 
<div style={{ display: "flex", minHeight: 400 }}>
  <Drawer variant="persistent" open={open} width={240} anchor="left">
    <nav>Side nav</nav>
  </Drawer>
  <main style={{ flex: 1, padding: 16 }}>
    <button type="button" onClick={() => setOpen((v) => !v)}>
      Toggle nav
    </button>
    {children}
  </main>
</div>

Permanent rail

<div style={{ display: "flex" }}>
  <Drawer variant="permanent" open width={200}>
    <nav>Always visible</nav>
  </Drawer>
  <main style={{ flex: 1 }}>{children}</main>
</div>

Hide backdrop / disable portal

// no dimmed overlay
<Drawer open={open} hideBackdrop onClose={close} width={260}>

</Drawer>
 
// keep in React tree (e.g. custom stacking context)
<Drawer open={open} disablePortal onClose={close} width={260}>

</Drawer>

Props

PropTypeDefault
openbooleanfalse
anchorleft | right | top | bottomleft
varianttemporary | persistent | permanenttemporary
widthnumber | string240
minifiedWidthnumber | string— (persistent mini variant)
hideBackdropbooleanfalse
disablePortalbooleanfalse
onClose() => void
onOpen() => void— (swipe)
SlotProps.backdropBackdropProps
sxSxProps