#Drawer
Open content from a page edge to view details or complete actions.
#Basic usage
basic.tsx
import { Button, Drawer, Flex, Form, FormField, Input, Select, Space } from '@ifhover/leaf-ui';
import { useState } from 'react';
export function DrawerBasic() {
const [placement, setPlacement] = useState<'left' | 'right' | 'top' | 'bottom'>('right');
const [open, setOpen] = useState(false);
return (
<Space direction="vertical" align="stretch">
<Flex gap="md" align="center" wrap justify="center">
{(['left', 'right', 'top', 'bottom'] as const).map((side) => (
<Button
key={side}
variant="outline"
onClick={() => {
setPlacement(side);
setOpen(true);
}}
>
{side}
</Button>
))}
</Flex>
<Drawer
open={open}
placement={placement}
title="Project settings"
onClose={() => setOpen(false)}
footer={
<>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button type="submit" form="drawer-project">
Save
</Button>
</>
}
>
<Form
id="drawer-project"
onSubmit={(event) => {
event.preventDefault();
setOpen(false);
}}
>
<FormField label="Project name" required>
<Input name="name" defaultValue="Leaf Garden" />
</FormField>
<FormField label="Project type">
<Select
name="type"
defaultValue="design"
options={[
{ value: 'design', label: 'Design' },
{ value: 'development', label: 'Development' },
]}
/>
</FormField>
</Form>
</Drawer>
</Space>
);
}
#Resize and nested push
capabilities.tsx
import { Button, Drawer, Input, Space } from '@ifhover/leaf-ui';
import { useState } from 'react';
export function Capabilities() {
const [open, setOpen] = useState(false),
[child, setChild] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>
{'Resizable workspace'}
</Button>
<Drawer
title={'Workspace'}
open={open}
onClose={() => setOpen(false)}
resizable
minSize={260}
maxSize={640}
>
<Space direction="vertical">
<Input aria-label="Project" />
<Button onClick={() => setChild(true)}>{'Open details'}</Button>
</Space>
<Drawer
open={child}
onClose={() => setChild(false)}
title={'Details'}
width={300}
push={120}
>
Details
</Drawer>
</Drawer>
</>
);
}
#Notification preferences
Separate the heading, settings and footer actions.
sections.tsx
import { Alert, Button, Drawer, RadioGroup, Space } from '@ifhover/leaf-ui';
import { Bell } from 'lucide-react';
import { useState } from 'react';
export function DrawerSections() {
const [open, setOpen] = useState(false);
const [frequency, setFrequency] = useState('weekly');
return (
<>
<Button variant="outline" startIcon={<Bell />} onClick={() => setOpen(true)}>
{'Notification preferences'}
</Button>
<Drawer
open={open}
width={440}
title={'Notification preferences'}
description={'Choose the right update frequency for you.'}
icon={<Bell />}
onClose={() => setOpen(false)}
footer={
<>
<Button variant="outline" onClick={() => setOpen(false)}>
{'Cancel'}
</Button>
<Button onClick={() => setOpen(false)}>{'Save'}</Button>
</>
}
>
<Space direction="vertical" align="stretch" size={20}>
<RadioGroup
variant="list"
label={'Activity summary'}
value={frequency}
onChange={(event) => setFrequency(event.target.value)}
options={[
{
value: 'daily',
label: 'Daily',
description: 'A daily team activity digest',
},
{
value: 'weekly',
label: 'Weekly',
description: 'A summary every Monday',
},
{
value: 'off',
label: 'No summary',
description: 'Receive only important updates',
},
]}
/>
<Alert
type="info"
title={'Important alerts stay enabled'}
description={'Security events and actions requiring your attention are always delivered.'}
/>
</Space>
</Drawer>
</>
);
}
#Drawer API
| Property | Type | Default | Description |
|---|---|---|---|
initialFocus | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | Specify initial focus after opening. |
returnFocus | boolean | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | Restore focus on close; specify a destination or use false. |
open | boolean | Required | Required. Whether the drawer is open |
title | ReactNode | — | Title |
description | ReactNode | — | Additional description. |
icon | ReactNode | — | Decorative icon to the left of the heading. |
divided | boolean | true | Separate the heading, content and footer with subtle borders. |
footer | ReactNode | — | Footer content |
extra | ReactNode | — | Actions beside the title |
placement | 'left' | 'right' | 'top' | 'bottom' | 'right' | Opening edge |
width | number | string | 380 | Width for left and right drawers |
height | number | string | 300 | Height for top and bottom drawers |
closable | boolean | true | Close button / backdrop click / Escape |
maskClosable | boolean | true | Close button / backdrop click / Escape |
keyboard | boolean | true | Close button / backdrop click / Escape |
onClose | () => void | — | Close request; update open in application state |
afterClose | () => void | — | Called after the closing transition |
forceRender | boolean | false | Mount before opening and keep hidden while closed. |
destroyOnClose | boolean | false | Unmount content after the closing transition; preserved by default. |
preserve | boolean | — | Explicitly preserve closed content; overrides destroyOnClose. |
container | Element | DocumentFragment | (() => Element | DocumentFragment) | — | Custom portal or scroll container. |
push | boolean | number | false | Push the parent overlay when a nested drawer opens; accepts a distance. |
resizable | boolean | false | Allow resizing by dragging or arrow keys. |
minSize | number | 240 | Minimum size in pixels. |
maxSize | number | 900 | Maximum size in pixels. |
onResize | (size: number) => void | — | Called while resizing. |
#Types
Import the component props and item types when preparing your own values.
import type { DrawerProps } from '@ifhover/leaf-ui';#Usage notes
Place actions in footer. A submit button can use its form attribute to target a form inside the drawer. Provide aria-label if there is no title. Nested dialogs are supported, and focus returns to the opener after closing.
#Surfaces and rich content
Use description and icon to explain the current task. The heading and muted footer have subtle dividers by default; divided={false} groups content with spacing.