Skip to content

Repository files navigation

DropDrawer

A responsive component that automatically switches between a dropdown menu on desktop and a drawer on mobile devices for shadcn/ui.

A drop-in replacement for shadcn/ui's DropdownMenu component.

Demo:https://dropdrawer.jiaweing.com

StarsForksLicenseIssues

Why DropDrawer?

Traditional dropdown menus don't feel native on mobile devices - they can be difficult to interact with on small screens and often create a poor user experience.

DropDrawer solves this problem by:

  • Automatically switching between dropdown menus on desktop
  • Using a native-feeling drawer interface on mobile devices
  • Providing consistent interaction patterns across all screen sizes
  • Using a responsive breakpoint of 768px (configurable via the hook)

Desktop View

Light ModeDark Mode
Desktop dropdown viewDesktop dropdown view dark

Mobile Views

Main DrawerSubmenu Navigation
Mobile drawer viewMobile drawer with submenu
Mobile drawer view darkMobile drawer with submenu dark

Installation

Using the GitHub registry (Recommended)

shadcn can install DropDrawer straight from this GitHub repository - no registry server or JSON URL required:

# pnpm
pnpm dlx shadcn@latest add jiaweing/DropDrawer/dropdrawer
# npm
npx shadcn@latest add jiaweing/DropDrawer/dropdrawer
# yarn
yarn dlx shadcn@latest add jiaweing/DropDrawer/dropdrawer
# bun
bunx shadcn@latest add jiaweing/DropDrawer/dropdrawer

You can pin to a branch, tag, or commit SHA with #ref:

npx shadcn@latest add jiaweing/DropDrawer/dropdrawer#main

Using the hosted registry URL

You can also install from the hosted registry JSON:

# pnpm
pnpm dlx shadcn@latest add https://dropdrawer.jiaweing.com/r/dropdrawer.json
# npm
npx shadcn@latest add https://dropdrawer.jiaweing.com/r/dropdrawer.json
# yarn
yarn dlx shadcn@latest add https://dropdrawer.jiaweing.com/r/dropdrawer.json
# bun
bunx shadcn@latest add https://dropdrawer.jiaweing.com/r/dropdrawer.json

During local development, you can use:

npx shadcn@latest add http://localhost:3000/r/dropdrawer.json

Either method will automatically:

  • Install all required dependencies
  • Add the component to your project
  • Set up all necessary configuration

Manual Installation

  1. Copy the dropdown-menu and drawer components from shadcn/ui.
# pnpm
pnpm dlx shadcn@latest add dropdown-menu drawer
# npm
npx shadcn@latest add dropdown-menu drawer
# yarn
yarn dlx shadcn@latest add dropdown-menu drawer
# bun
bunx shadcn@latest add dropdown-menu drawer

Alternatively, if you are not using shadcn/ui cli, you can manually copy the components from shadcn/ui.

If you copied the drawer component manually, make sure to install vaul:

npm install vaul
  1. Copy the useIsMobile hook:
Click to show code
import*asReactfrom"react";constMOBILE_BREAKPOINT=768;exportfunctionuseIsMobile(){const[isMobile,setIsMobile]=React.useState<boolean|undefined>(undefined);React.useEffect(()=>{constmql=window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT-1}px)`);constonChange=()=>{setIsMobile(window.innerWidth<MOBILE_BREAKPOINT);};mql.addEventListener("change",onChange);setIsMobile(window.innerWidth<MOBILE_BREAKPOINT);return()=>mql.removeEventListener("change",onChange);},[]);return!!isMobile;}
  1. Copy the dropdrawer component:
Click to show code
"use client";import{ChevronRightIcon}from"lucide-react";import*asReactfrom"react";import{Drawer,DrawerClose,DrawerContent,DrawerFooter,DrawerHeader,DrawerTitle,DrawerTrigger,}from"@/components/ui/drawer";import{DropdownMenu,DropdownMenuContent,DropdownMenuItem,DropdownMenuLabel,DropdownMenuSeparator,DropdownMenuSub,DropdownMenuSubContent,DropdownMenuSubTrigger,DropdownMenuTrigger,}from"@/components/ui/dropdown-menu";import{useIsMobile}from"@/hooks/use-mobile";import{cn}from"@/lib/utils";// Component code here - see full implementation in the repository
  1. Update the import paths based on your project structure.

  2. Customize the mobile breakpoint (optional):

The default mobile breakpoint is 768px. You can customize this by modifying the MOBILE_BREAKPOINT constant in the use-mobile.ts hook.

Usage

Migrating from DropdownMenu

DropDrawer is designed as a drop-in replacement for shadcn/ui's DropdownMenu component. You can easily migrate your existing DropdownMenu components to DropDrawer with a simple find-and-replace:

  1. Replace imports:
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";+ import { DropDrawer, DropDrawerContent, DropDrawerItem, DropDrawerTrigger } from "@/components/ui/dropdrawer";
  1. Replace component names:
- <DropdownMenu>- <DropdownMenuTrigger>- <DropdownMenuContent>- <DropdownMenuItem>+ <DropDrawer>+ <DropDrawerTrigger>+ <DropDrawerContent>+ <DropDrawerItem>

The component API is designed to match DropdownMenu, so most props should work without changes.

Here's the mapping between DropdownMenu and DropDrawer components:

DropdownMenu ComponentDropDrawer Component
DropdownMenuDropDrawer
DropdownMenuTriggerDropDrawerTrigger
DropdownMenuContentDropDrawerContent
DropdownMenuItemDropDrawerItem
DropdownMenuLabelDropDrawerLabel
DropdownMenuSeparatorDropDrawerSeparator
DropdownMenuGroupDropDrawerGroup
DropdownMenuSubDropDrawerSub
DropdownMenuSubTriggerDropDrawerSubTrigger
DropdownMenuSubContentDropDrawerSubContent

Note: Some advanced DropdownMenu components like DropdownMenuCheckboxItem, DropdownMenuRadioGroup, and DropdownMenuRadioItem are not currently implemented in DropDrawer.

Real-world Example: Theme Switcher

Here's how to convert a theme switcher from DropdownMenu to DropDrawer:

// Before: Using DropdownMenuimport{Moon,Sun}from"lucide-react";import{useTheme}from"@/components/theme-provider";import{Button}from"@/components/ui/button";import{DropdownMenu,DropdownMenuContent,DropdownMenuItem,DropdownMenuTrigger,}from"@/components/ui/dropdown-menu";exportfunctionModeToggle(){const{ setTheme }=useTheme();return(<DropdownMenu><DropdownMenuTriggerasChild><Buttonvariant="outline"size="icon"className="rounded-full"><SunclassName="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"/><MoonclassName="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"/><spanclassName="sr-only">Toggle theme</span></Button></DropdownMenuTrigger><DropdownMenuContentalign="end"><DropdownMenuItemonClick={()=>setTheme("light")}>
Light
</DropdownMenuItem><DropdownMenuItemonClick={()=>setTheme("dark")}>
Dark
</DropdownMenuItem><DropdownMenuItemonClick={()=>setTheme("system")}>
System
</DropdownMenuItem></DropdownMenuContent></DropdownMenu>);}
// After: Using DropDrawerimport{Moon,Sun}from"lucide-react";import{useTheme}from"@/components/theme-provider";import{Button}from"@/components/ui/button";import{DropDrawer,DropDrawerContent,DropDrawerItem,DropDrawerTrigger,}from"@/components/ui/dropdrawer";exportfunctionModeToggle(){const{ setTheme }=useTheme();return(<DropDrawer><DropDrawerTriggerasChild><Buttonvariant="outline"size="icon"className="rounded-full"><SunclassName="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"/><MoonclassName="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"/><spanclassName="sr-only">Toggle theme</span></Button></DropDrawerTrigger><DropDrawerContentalign="end"><DropDrawerItemonClick={()=>setTheme("light")}>Light</DropDrawerItem><DropDrawerItemonClick={()=>setTheme("dark")}>Dark</DropDrawerItem><DropDrawerItemonClick={()=>setTheme("system")}>
System
</DropDrawerItem></DropDrawerContent></DropDrawer>);}

Basic Example

Create a simple dropdown/drawer menu with just a few lines of code:

import{DropDrawer,DropDrawerContent,DropDrawerItem,DropDrawerTrigger,}from"@/components/ui/dropdrawer";import{Button}from"@/components/ui/button";exportfunctionExample(){return(<DropDrawer><DropDrawerTriggerasChild><Button>Open Menu</Button></DropDrawerTrigger><DropDrawerContent><DropDrawerItem>Item 1</DropDrawerItem><DropDrawerItem>Item 2</DropDrawerItem><DropDrawerItem>Item 3</DropDrawerItem></DropDrawerContent></DropDrawer>);}

Using State to Control the Menu

You can control the open state of the menu using React state:

import*asReactfrom"react";import{DropDrawer,DropDrawerContent,DropDrawerItem,DropDrawerTrigger,}from"@/components/ui/dropdrawer";import{Button}from"@/components/ui/button";exportfunctionStateExample(){const[open,setOpen]=React.useState(false);consthandleOpen=()=>{setOpen(true);};return(<><ButtononClick={handleOpen}>Open with State</Button><DropDraweropen={open}onOpenChange={setOpen}><DropDrawerContent><DropDrawerItem>Item 1</DropDrawerItem><DropDrawerItem>Item 2</DropDrawerItem><DropDrawerItem>Item 3</DropDrawerItem></DropDrawerContent></DropDrawer></>);}

Advanced Usage with Nested Submenus

Create complex navigation structures with nested submenus:

import{DropDrawer,DropDrawerContent,DropDrawerItem,DropDrawerSub,DropDrawerSubContent,DropDrawerSubTrigger,DropDrawerTrigger,}from"@/components/ui/dropdrawer";import{Button}from"@/components/ui/button";exportfunctionNestedExample(){return(<DropDrawer><DropDrawerTriggerasChild><Button>Open Menu</Button></DropDrawerTrigger><DropDrawerContent><DropDrawerItem>Item 1</DropDrawerItem><DropDrawerSub><DropDrawerSubTrigger>Submenu</DropDrawerSubTrigger><DropDrawerSubContent><DropDrawerItem>Submenu Item 1</DropDrawerItem><DropDrawerItem>Submenu Item 2</DropDrawerItem></DropDrawerSubContent></DropDrawerSub><DropDrawerItem>Item 3</DropDrawerItem></DropDrawerContent></DropDrawer>);}

Complex Example with Groups and Icons

Here's a more complex example showing how to use groups, icons, and multiple nested submenus:

import{AlertTriangle,BookmarkIcon,Copy,EyeOffIcon,Home,LayoutDashboard,MoreVertical,Settings,ShieldIcon,UserMinusIcon,UserXIcon,}from"lucide-react";import{useState}from"react";import{Button}from"@/components/ui/button";import{DropDrawer,DropDrawerContent,DropDrawerGroup,DropDrawerItem,DropDrawerSeparator,DropDrawerSub,DropDrawerSubContent,DropDrawerSubTrigger,DropDrawerTrigger,}from"@/components/ui/dropdrawer";exportfunctionPostExample(){const[open,setOpen]=useState(false);return(<DropDraweropen={open}onOpenChange={setOpen}><DropDrawerTriggerasChild><Buttonvariant="ghost"size="icon"className="h-8 w-8 rounded-full"><MoreVerticalclassName="h-5 w-5"/></Button></DropDrawerTrigger><DropDrawerContent>{/* First group: Add to feed, Save, Not interested */}<DropDrawerGroup><DropDrawerSub><DropDrawerSubTriggericon={<LayoutDashboardclassName="h-5 w-5"/>}>
Add to feed
</DropDrawerSubTrigger><DropDrawerSubContent><DropDrawerItemicon={<HomeclassName="h-5 w-5"/>}>
Home
</DropDrawerItem><DropDrawerItemicon={<LayoutDashboardclassName="h-5 w-5"/>}>
Work
</DropDrawerItem><DropDrawerItemicon={<BookmarkIconclassName="h-5 w-5"/>}>
Personal
</DropDrawerItem></DropDrawerSubContent></DropDrawerSub><DropDrawerItemicon={<BookmarkIconclassName="h-5 w-5"/>}>
Save
</DropDrawerItem><DropDrawerItemicon={<EyeOffIconclassName="h-5 w-5"/>}>
Not interested
</DropDrawerItem></DropDrawerGroup><DropDrawerSeparator/>{/* Second group */}<DropDrawerGroup><DropDrawerItemicon={<UserMinusIconclassName="h-5 w-5"/>}>
Mute
</DropDrawerItem><DropDrawerItemicon={<CopyclassName="h-5 w-5"/>}>
Copy link
</DropDrawerItem></DropDrawerGroup></DropDrawerContent></DropDrawer>);}

API Reference

Component Overview

ComponentDescription
DropDrawerRoot component that manages state
DropDrawerTriggerButton that opens the dropdown/drawer
DropDrawerContentContainer for dropdown/drawer content
DropDrawerItemIndividual menu item
DropDrawerSubContainer for submenu
DropDrawerSubTriggerButton that opens a submenu
DropDrawerSubContentContainer for submenu content
DropDrawerSeparatorVisual separator between items
DropDrawerGroupGroups related menu items
DropDrawerFooterFooter section for the drawer

DropDrawer

The root component that manages the state of the dropdown/drawer.

PropTypeDescription
openbooleanControls the open state
onOpenChange(open: boolean) => voidCallback when open state changes
childrenReact.ReactNodeThe content of the component

DropDrawerTrigger

The button that triggers the dropdown/drawer.

PropTypeDescription
asChildbooleanWhether to merge props with the child element
childrenReact.ReactNodeThe content of the trigger

DropDrawerContent

The content of the dropdown/drawer.

PropTypeDescription
classNamestringAdditional CSS classes
childrenReact.ReactNodeThe content of the dropdown/drawer

DropDrawerItem

An item in the dropdown/drawer.

PropTypeDescription
onSelect(event: Event) => voidCallback when item is selected
onClickReact.MouseEventHandler<HTMLDivElement>Callback when item is clicked
iconReact.ReactNodeIcon to display on the left side of item
variant"default" | "destructive"Visual style variant
insetbooleanWhether to add left padding
disabledbooleanWhether the item is disabled
childrenReact.ReactNodeThe content of the item

DropDrawerSub

A container for submenu content.

PropTypeDescription
idstringOptional ID for the submenu (auto-generated if not provided)
childrenReact.ReactNodeThe content of the submenu (should include SubTrigger and SubContent)

DropDrawerFooter

A footer section for the drawer. This component is only rendered in mobile mode (drawer) and is ignored in desktop mode (dropdown).

PropTypeDescription
classNamestringAdditional CSS classes
childrenReact.ReactNodeThe content of the footer

Credits

Star History

Star History Chart

About

A responsive component that automatically switches between a dropdown menu on desktop and a drawer on mobile devices for shadcn/ui. A drop-in replacement for shadcn/ui's DropdownMenu component.

Resources

Code of conduct

Contributing

Security policy

Stars

204 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages