ToggleDrawer
freeToggleDrawer moves supplementary navigation or settings content into an edge drawer behind a compact trigger.
@floating-ui/reactmotion
Preview
Dark
Workspace
Settings menu moves off-canvas on mobile.
import { type ReactNode } from 'react';
import { PiBell, PiCreditCard, PiGear, PiUser, PiUsers } from 'react-icons/pi'
import Menu from '@/components/ui/Menu';
import ToggleDrawer from '@/components/composites/ToggleDrawer';
const MenuLabel = ({
icon,
children,
}: {
icon: ReactNode;
children: ReactNode;
}) => (
<span className="flex min-w-0 items-center gap-2">
{icon}
<span className="truncate">{children}</span>
</span>
);
export default function UsageDemo() {
return (
<div className="flex w-full max-w-sm items-center justify-between gap-4 rounded-card border border-border p-4">
<div className="min-w-0">
<p className="font-medium">Workspace</p>
<p className="truncate text-sm text-muted-foreground">
Settings menu moves off-canvas on mobile.
</p>
</div>
<ToggleDrawer title="Settings" placement="left">
<Menu
variant="subtle"
defaultActiveKeys={['profile']}
defaultExpandedKeys={['account']}
>
<Menu.MenuItem eventKey="profile">
<MenuLabel icon={<PiUser className="text-base" />}>
Profile
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="notifications">
<MenuLabel icon={<PiBell className="text-base" />}>
Notifications
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuCollapse
eventKey="account"
label={
<MenuLabel icon={<PiGear className="text-base" />}>
Account
</MenuLabel>
}
>
<Menu.MenuItem eventKey="billing">
<MenuLabel icon={<PiCreditCard className="text-base" />}>
Billing
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="team">
<MenuLabel icon={<PiUsers className="text-base" />}>
Team
</MenuLabel>
</Menu.MenuItem>
</Menu.MenuCollapse>
</Menu>
</ToggleDrawer>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add ToggleDrawerAPI
ToggleDrawer owns its open state and accepts Drawer props except isOpen. The built-in trigger toggles the drawer; use the custom ref when another control needs to open or close it.
| Prop | Description | Type | Default |
|---|---|---|---|
children | Content rendered in the scrollable drawer body. | ReactNode | — |
placement | Edge the drawer opens from. Top and bottom placements add a drag handle. | DrawerPlacement | 'left' |
title | Content rendered in the drawer header. | `string | ReactNode` |
bodyClass | Extra classes applied to the scrollable body. | string | — |
closable | Shows the default close button in the header. | boolean | true |
contentClassName | Classes applied to the drawer surface. | string | — |
footer | Content rendered in the fixed footer region. | `string | ReactNode` |
footerClass | Extra classes applied to the footer. | string | — |
headerClass | Extra classes applied to the header. | string | — |
height | Drawer height for top or bottom placement. | `string | number` |
lockScroll | Locks document scrolling while the overlay is mounted. | boolean | true |
onClose | Callback forwarded to Drawer for close requests. Supplying it replaces ToggleDrawer's internal close handler. | DrawerOpenChangeHandler | — |
onOpen | Callback forwarded to Drawer when it opens. | DrawerOpenChangeHandler | — |
overlayClassName | Classes applied to the backdrop overlay. | string | — |
ref | Imperative controls for opening and closing the drawer. | Ref<ToggleDrawerRef> | — |
shouldCloseOnEsc | Allows Escape to request closing. | boolean | true |
shouldCloseOnOverlayClick | Allows an overlay click to request closing. | boolean | true |
width | Drawer width for left or right placement. | number | 280 |
...nativeDivProps | Native props forwarded to the drawer content element. | NativeDrawerDivProps | — |
isOpen is intentionally not part of ToggleDrawer's API because the component manages that state internally.
Types
type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left';
type DrawerOpenChangeHandler = (open: boolean) => void;
type ToggleDrawerRef = {
handleCloseDrawer: () => void;
handleOpenDrawer: () => void;
};
type NativeDrawerDivProps = Omit<ComponentProps<'div'>, 'title'>;