For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/drawer.md.
  • English
  • 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

    PropertyTypeDefaultDescription
    initialFocusRefObject<HTMLElement | null> | (() => HTMLElement | null)—Specify initial focus after opening.
    returnFocusboolean | RefObject<HTMLElement | null> | (() => HTMLElement | null)—Restore focus on close; specify a destination or use false.
    openbooleanRequiredRequired. Whether the drawer is open
    titleReactNode—Title
    descriptionReactNode—Additional description.
    iconReactNode—Decorative icon to the left of the heading.
    dividedbooleantrueSeparate the heading, content and footer with subtle borders.
    footerReactNode—Footer content
    extraReactNode—Actions beside the title
    placement'left' | 'right' | 'top' | 'bottom''right'Opening edge
    widthnumber | string380Width for left and right drawers
    heightnumber | string300Height for top and bottom drawers
    closablebooleantrueClose button / backdrop click / Escape
    maskClosablebooleantrueClose button / backdrop click / Escape
    keyboardbooleantrueClose button / backdrop click / Escape
    onClose() => void—Close request; update open in application state
    afterClose() => void—Called after the closing transition
    forceRenderbooleanfalseMount before opening and keep hidden while closed.
    destroyOnClosebooleanfalseUnmount content after the closing transition; preserved by default.
    preserveboolean—Explicitly preserve closed content; overrides destroyOnClose.
    containerElement | DocumentFragment | (() => Element | DocumentFragment)—Custom portal or scroll container.
    pushboolean | numberfalsePush the parent overlay when a nested drawer opens; accepts a distance.
    resizablebooleanfalseAllow resizing by dragging or arrow keys.
    minSizenumber240Minimum size in pixels.
    maxSizenumber900Maximum 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.