Gecko UIGecko UI

Drawer

Slide-out panel component that displays content from any edge of the viewport

Drawer

A slide-out panel component that displays auxiliary content from any edge of the viewport. Provides a less intrusive alternative to modals for navigation menus, settings panels, filters, and contextual information with smooth transitions and flexible dismissal behaviors.

Installation

import { Drawer } from '@geckoui/geckoui';

Basic Usage

import { useState } from 'react';
import { Drawer, Button } from '@geckoui/geckoui';

function Example() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Drawer</Button>
      <Drawer
        open={open}
        handleClose={() => setOpen(false)}
        placement="right"
        allowClickOutside
        className="w-80 p-6"
      >
        <h2 className="text-xl font-semibold mb-4">Drawer Content</h2>
        <p className="text-gray-600 mb-4">
          This is a basic drawer that slides in from the right side.
        </p>
        <Button onClick={() => setOpen(false)}>Close</Button>
      </Drawer>
    </>
  );
}

Props API

PropTypeDefaultDescription
openbooleanfalseControl drawer visibility (required)
handleClose() => void-Callback when drawer should close
placement'top' | 'right' | 'bottom' | 'left''right'Edge from which drawer slides
allowClickOutsidebooleanfalseAllow clicking backdrop to close
dismissOnEscapebooleantrueClose drawer on Escape key press
hideBackdropbooleanfalseHide backdrop overlay (visual only)
backdropClassNamestring-CSS class for backdrop
classNamestring-CSS class for drawer panel
childrenReactNode-Drawer content

Examples

Mobile Navigation Menu

const [menuOpen, setMenuOpen] = useState(false);

<Button onClick={() => setMenuOpen(true)}>
  <MenuIcon /> Menu
</Button>

<Drawer
  open={menuOpen}
  handleClose={() => setMenuOpen(false)}
  placement="left"
  allowClickOutside
  dismissOnEscape
  className="w-80"
>
  <nav className="p-6">
    <h2 className="text-lg font-semibold mb-4">Navigation</h2>
    <ul className="space-y-2">
      <li><a href="#" className="block py-2 px-3 rounded hover:bg-gray-100">Home</a></li>
      <li><a href="#" className="block py-2 px-3 rounded hover:bg-gray-100">Products</a></li>
      <li><a href="#" className="block py-2 px-3 rounded hover:bg-gray-100">About</a></li>
    </ul>
  </nav>
</Drawer>

Filter Panel

import { Input, Label, Select, SelectOption } from '@geckoui/geckoui';

const [filtersOpen, setFiltersOpen] = useState(false);
const [category, setCategory] = useState('');

<Drawer
  open={filtersOpen}
  handleClose={() => setFiltersOpen(false)}
  placement="right"
  allowClickOutside
  backdropClassName="bg-black/60"
  className="w-96 p-6"
>
  <h2 className="text-xl font-semibold mb-4">Filters</h2>
  <div className="space-y-4">
    <div className="space-y-1">
      <Label>Price Range</Label>
      <div className="flex gap-2">
        <Input type="number" placeholder="Min" />
        <Input type="number" placeholder="Max" />
      </div>
    </div>
    <div className="space-y-1">
      <Label>Category</Label>
      <Select value={category} onChange={setCategory}>
        <SelectOption value="" label="All Categories" />
        <SelectOption value="electronics" label="Electronics" />
        <SelectOption value="clothing" label="Clothing" />
        <SelectOption value="books" label="Books" />
      </Select>
    </div>
    <div className="flex gap-2 pt-4">
      <Button variant="ghost" onClick={() => setFiltersOpen(false)}>Cancel</Button>
      <Button onClick={() => setFiltersOpen(false)}>Apply Filters</Button>
    </div>
  </div>
</Drawer>

Notification Center (Top)

<Drawer
  open={showNotifications}
  handleClose={() => setShowNotifications(false)}
  placement="top"
  hideBackdrop={false}
  allowClickOutside
  className="h-96 border-b"
>
  <div className="p-6">
    <h2 className="text-xl font-semibold mb-4">Notifications</h2>
    <div className="space-y-3">
      <div className="p-3 border rounded-md">
        <p className="font-medium">New message received</p>
        <p className="text-sm text-gray-600">5 minutes ago</p>
      </div>
    </div>
  </div>
</Drawer>

Bottom Sheet

<Drawer
  open={isBottomSheetOpen}
  handleClose={() => setBottomSheetOpen(false)}
  placement="bottom"
  allowClickOutside
  backdropClassName="bg-black/40"
  className="h-64 rounded-t-2xl"
>
  <div className="p-6">
    <h3 className="text-lg font-semibold mb-4">Quick Actions</h3>
    <div className="space-y-2">
      <button className="w-full text-left py-3 px-4 rounded-md hover:bg-gray-100">
        Share
      </button>
      <button className="w-full text-left py-3 px-4 rounded-md hover:bg-gray-100">
        Edit
      </button>
    </div>
  </div>
</Drawer>

Without Backdrop

import { Input, Label } from '@geckoui/geckoui';

<Drawer
  open={settingsVisible}
  handleClose={() => setSettingsVisible(false)}
  placement="right"
  hideBackdrop
  allowClickOutside={false}
  className="w-[600px] border-l"
>
  <div className="p-6">
    <h2 className="text-xl font-semibold mb-4">Settings</h2>
    <div className="space-y-4">
      <div className="space-y-1">
        <Label htmlFor="profile">Profile</Label>
        <Input id="profile" type="text" defaultValue="John Doe" />
      </div>
      <div className="space-y-1">
        <Label htmlFor="settings-email">Email</Label>
        <Input id="settings-email" type="email" defaultValue="john@example.com" />
      </div>
      <Button onClick={() => setSettingsVisible(false)}>Save Settings</Button>
    </div>
  </div>
</Drawer>

Imperative API

Drawer also provides an imperative API for displaying drawers without managing React state:

// Show drawer imperatively
Drawer.show(
  <QuickActionsMenu
    actions={globalActions}
    onActionClick={(action) => {
      handleAction(action);
      Drawer.dismiss();
    }}
  />,
  {
    placement: "bottom",
    handleClose: () => Drawer.dismiss(),
    className: "h-80 rounded-t-xl"
  }
);

// Dismiss drawer
Drawer.dismiss();

Contextual Help Panel

Drawer.show(
  <HelpDocumentation topic={currentTopic} />,
  {
    placement: "right",
    handleClose: () => {
      logHelpUsage(currentTopic);
      Drawer.dismiss();
    },
    allowClickOutside: true,
    className: "w-[500px]"
  }
);

Placement Options

The drawer can slide in from any edge of the viewport:

  • "right" - Slides in from the right (default)
  • "left" - Slides in from the left
  • "top" - Slides down from the top
  • "bottom" - Slides up from the bottom

Behavior

Click Outside

When allowClickOutside is enabled, clicking the backdrop or any element outside the drawer will close it. Use with caution as clickable elements beneath the drawer will also trigger the close action.

Escape Key

By default, pressing the Escape key will close the drawer. Disable this with dismissOnEscape={false} for critical workflows.

Backdrop

The backdrop overlay can be:

  • Visible (default) - Shows a semi-transparent overlay
  • Hidden - Set hideBackdrop={true} to hide the backdrop visually
  • Custom - Use backdropClassName for custom styling

Portal Rendering

Drawer requires <GeckoUIPortal /> to be mounted when using the imperative API:

// app/layout.tsx
import { GeckoUIPortal } from '@geckoui/geckoui';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <GeckoUIPortal />
      </body>
    </html>
  );
}

Styling

The component uses BEM-style class names:

  • GeckoUIDrawer - Main container
  • GeckoUIDrawer__backdrop - Background overlay
  • GeckoUIDrawer__backdrop--visible - Visible backdrop state
  • GeckoUIDrawer__backdrop--hidden - Hidden backdrop state
  • GeckoUIDrawer__backdrop--clickthrough - Clickable backdrop
  • GeckoUIDrawer__drawer - Drawer panel
  • GeckoUIDrawer__drawer--{placement} - Placement modifier
  • GeckoUIDrawer__drawer--open - Open state
  • GeckoUIDrawer__drawer--closed - Closed state

Accessibility

  • Keyboard navigation with Escape key support
  • Click-outside detection
  • Focus management
  • ARIA role="dialog" applied
  • Backdrop prevents interaction with underlying content when visible
  • Dialog - Centered modal dialogs
  • Menu - Dropdown menus and action lists