MC Drawer

A slide-out panel for navigation or content.

Overview

The mc-drawer component is a responsive slide-out panel for navigation, settings, forms, and secondary workflows. It is built on top of vaul, with MicroClub styling for overlays, side panels, bottom sheets, nav rows, headers, footers, and accessible close controls.

Use it when content should stay close to the current page without taking the user into a full route change.

Preview

Loading...
'use client';import { Moon } from 'lucide-react';import {  McDrawer,  McDrawerClose,  McDrawerContent,  McDrawerDescription,  McDrawerHeader,  McDrawerNav,  McDrawerNavItem,  McDrawerTitle,  McDrawerTrigger,} from '../ui/mc-drawer';const navItems = [  { label: 'Account', hasArrow: true },  { label: 'Socials', hasArrow: false },  { label: 'Settings', hasArrow: true },  { label: 'Help', hasArrow: true },  { label: 'Info', hasArrow: true },];function DesktopWithIcons() {  return (    <McDrawer direction="right">      <McDrawerTrigger className="inline-flex items-center justify-center rounded-full bg-primary px-5 py-3.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50">        Drawer — icons      </McDrawerTrigger>      <McDrawerContent>        <McDrawerTitle className="sr-only">Navigation — icons</McDrawerTitle>        <McDrawerNav />        <div className="flex flex-1 flex-col gap-0 overflow-y-auto px-0 pt-9">          <McDrawerNavItem icon={<Moon size={16} />} rightElement={<ToggleSwitch />}>            Dark mode          </McDrawerNavItem>          {navItems.map(({ label, hasArrow }) => (            <McDrawerNavItem key={label} icon={<Moon size={16} />} hasArrow={hasArrow}>              {label}            </McDrawerNavItem>          ))}        </div>      </McDrawerContent>    </McDrawer>  );}function DesktopNoIcons() {  return (    <McDrawer direction="right">      <McDrawerTrigger className="inline-flex items-center justify-center rounded-full bg-primary px-5 py-3.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50">        Drawer — no icons      </McDrawerTrigger>      <McDrawerContent>        <McDrawerTitle className="sr-only">Navigation — no icons</McDrawerTitle>        <McDrawerNav />        <div className="flex flex-1 flex-col gap-0 overflow-y-auto px-0 pt-9">          <McDrawerNavItem rightElement={<ToggleSwitch />}>Dark mode</McDrawerNavItem>          {navItems.map(({ label, hasArrow }) => (            <McDrawerNavItem key={label} hasArrow={hasArrow}>              {label}            </McDrawerNavItem>          ))}        </div>      </McDrawerContent>    </McDrawer>  );}function MobileSheet() {  return (    <McDrawer direction="bottom">      <McDrawerTrigger className="inline-flex items-center justify-center rounded-full bg-primary px-5 py-3.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50">        Drawer — mobile      </McDrawerTrigger>      <McDrawerContent>        <div className="flex flex-col gap-6 px-4 py-6">          <McDrawerHeader className="gap-2 p-0">            <McDrawerTitle>Edit profile</McDrawerTitle>            <McDrawerDescription>              Make changes to your profile here. Click save when you&apos;re done.            </McDrawerDescription>          </McDrawerHeader>          <div className="flex flex-col gap-4">            <div className="flex flex-col gap-1.5">              <label htmlFor="drawer-email" className="text-sm font-medium text-foreground">                Email              </label>              <input                id="drawer-email"                type="email"                defaultValue="m@example.com"                className="w-full rounded-lg border border-border bg-input px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring"              />            </div>            <div className="flex flex-col gap-1.5">              <label htmlFor="drawer-username" className="text-sm font-medium text-foreground">                Username              </label>              <input                id="drawer-username"                type="text"                defaultValue="m@example.com"                className="w-full rounded-lg border border-border bg-input px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring"              />            </div>          </div>          <McDrawerClose className="w-full rounded-lg bg-primary py-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50">            Save changes          </McDrawerClose>        </div>      </McDrawerContent>    </McDrawer>  );}export default function DrawerDemo() {  return (    <div className="flex flex-wrap gap-4">      <DesktopWithIcons />      <DesktopNoIcons />      <MobileSheet />    </div>  );}function ToggleSwitch() {  return (    <div      role="switch"      aria-checked="false"      tabIndex={0}      className="relative h-6 w-10 shrink-0 cursor-pointer rounded-full bg-muted focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"    >      <span className="absolute left-1 top-1 h-4 w-4 rounded-full bg-background shadow-sm transition-transform" />    </div>  );}

Add to Your Project

Deploy mc-drawer directly to your components/ui directory:

npx mcoli-ui@latest add mc-drawer
pnpm dlx mcoli-ui@latest add mc-drawer
yarn dlx mcoli-ui@latest add mc-drawer
bunx mcoli-ui@latest add mc-drawer

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install vaul
pnpm add vaul
yarn add vaul
bun add vaul
Copy the source code
'use client';import * as React from 'react';import { Drawer as McDrawerPrimitive } from 'vaul';import { cn } from '@/lib/utils';function McDrawer({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Root>) {  return <McDrawerPrimitive.Root data-slot="drawer" {...props} />;}function McDrawerTrigger({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Trigger>) {  return <McDrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;}function McDrawerPortal({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Portal>) {  return <McDrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;}function McDrawerClose({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Close>) {  return <McDrawerPrimitive.Close data-slot="drawer-close" {...props} />;}function McDrawerOverlay({  className,  ...props}: React.ComponentProps<typeof McDrawerPrimitive.Overlay>) {  return (    <McDrawerPrimitive.Overlay      data-slot="drawer-overlay"      className={cn(        'fixed inset-0 z-50 bg-foreground/20',        'supports-backdrop-filter:backdrop-blur-xs',        'data-open:animate-in data-open:fade-in-0',        'data-closed:animate-out data-closed:fade-out-0',        className      )}      {...props}    />  );}function McDrawerContent({  className,  children,  ...props}: React.ComponentProps<typeof McDrawerPrimitive.Content>) {  return (    <McDrawerPortal>      <McDrawerOverlay />      <McDrawerPrimitive.Content        data-slot="drawer-content"        className={cn(          'group/drawer-content fixed z-50 flex flex-col',          'bg-popover text-popover-foreground',          'shadow-2xl',          'data-[vaul-drawer-direction=bottom]:inset-x-0',          'data-[vaul-drawer-direction=bottom]:bottom-0',          'data-[vaul-drawer-direction=bottom]:max-h-dvh',          'data-[vaul-drawer-direction=bottom]:rounded-t-2xl',          'data-[vaul-drawer-direction=bottom]:border',          'data-[vaul-drawer-direction=bottom]:border-border',          'data-[vaul-drawer-direction=top]:inset-x-0',          'data-[vaul-drawer-direction=top]:top-0',          'data-[vaul-drawer-direction=top]:max-h-dvh',          'data-[vaul-drawer-direction=top]:rounded-b-2xl',          'data-[vaul-drawer-direction=top]:border',          'data-[vaul-drawer-direction=top]:border-border',          'data-[vaul-drawer-direction=left]:inset-y-0',          'data-[vaul-drawer-direction=left]:left-0',          'data-[vaul-drawer-direction=left]:w-drawer',          'data-[vaul-drawer-direction=left]:max-w-full',          'data-[vaul-drawer-direction=left]:rounded-r-2xl',          'data-[vaul-drawer-direction=left]:border',          'data-[vaul-drawer-direction=left]:border-border',          'data-[vaul-drawer-direction=right]:inset-y-0',          'data-[vaul-drawer-direction=right]:right-0',          'data-[vaul-drawer-direction=right]:w-drawer',          'data-[vaul-drawer-direction=right]:max-w-full',          'data-[vaul-drawer-direction=right]:rounded-l-2xl',          'data-[vaul-drawer-direction=right]:border',          'data-[vaul-drawer-direction=right]:border-border',          className        )}        {...props}      >        <div className="mx-auto mt-4 hidden h-1 w-10 shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block group-data-[vaul-drawer-direction=top]/drawer-content:block" />        {children}      </McDrawerPrimitive.Content>    </McDrawerPortal>  );}function McDrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {  return (    <div      data-slot="drawer-header"      className={cn('flex flex-col gap-2 px-4 pt-0 pb-0', className)}      {...props}    />  );}function McDrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {  return (    <div      data-slot="drawer-footer"      className={cn('mt-auto flex flex-col gap-2 p-4 pt-0', className)}      {...props}    />  );}function McDrawerTitle({  className,  ...props}: React.ComponentProps<typeof McDrawerPrimitive.Title>) {  return (    <McDrawerPrimitive.Title      data-slot="drawer-title"      className={cn('text-base font-semibold text-foreground leading-snug', className)}      {...props}    />  );}function McDrawerDescription({  className,  ...props}: React.ComponentProps<typeof McDrawerPrimitive.Description>) {  return (    <McDrawerPrimitive.Description      data-slot="drawer-description"      className={cn('text-sm text-muted-foreground leading-relaxed', className)}      {...props}    />  );}function McDrawerNav({ className, ...props }: React.ComponentProps<'div'>) {  return (    <div      data-slot="drawer-nav"      className={cn('flex h-drawer-nav items-center gap-2 px-6 py-2.5', className)}      {...props}    >      <McDrawerClose        className="flex h-8 w-8 items-center justify-center rounded-md text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"        aria-label="Go back"      >        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">          <path            d="M12.5 15L7.5 10L12.5 5"            stroke="currentColor"            strokeWidth="1.5"            strokeLinecap="round"            strokeLinejoin="round"          />        </svg>      </McDrawerClose>    </div>  );}interface McDrawerNavItemProps extends React.ComponentProps<'button'> {  icon?: React.ReactNode;  hasArrow?: boolean;  rightElement?: React.ReactNode;}function McDrawerNavItem({  icon,  hasArrow = false,  rightElement,  children,  className,  ...props}: McDrawerNavItemProps) {  return (    <button      data-slot="drawer-nav-item"      className={cn(        'flex h-drawer-item w-full items-center justify-between rounded-xl',        'px-6 py-5',        'text-sm text-foreground',        'transition-colors hover:bg-accent hover:text-accent-foreground',        'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50',        className      )}      {...props}    >      <span className="flex items-center gap-3">        {icon && (          <span className="shrink-0 text-foreground" aria-hidden="true">            {icon}          </span>        )}        {children}      </span>      {rightElement ??        (hasArrow && (          <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">            <path              d="M6 4L10 8L6 12"              stroke="currentColor"              strokeWidth="1.5"              strokeLinecap="round"              strokeLinejoin="round"            />          </svg>        ))}    </button>  );}export {  McDrawer,  McDrawerPortal,  McDrawerOverlay,  McDrawerTrigger,  McDrawerClose,  McDrawerContent,  McDrawerHeader,  McDrawerFooter,  McDrawerTitle,  McDrawerDescription,  McDrawerNav,  McDrawerNavItem,};
Adjust import paths

Ensure your @/lib/utils or equivalent classname helper is correctly imported.

Usage

import {
  McDrawer,
  McDrawerContent,
  McDrawerDescription,
  McDrawerHeader,
  McDrawerTitle,
  McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export default function Example() {
  return (
    <McDrawer direction="right">
      <McDrawerTrigger>Open drawer</McDrawerTrigger>
      <McDrawerContent>
        <McDrawerHeader>
          <McDrawerTitle>Settings</McDrawerTitle>
          <McDrawerDescription>Update your workspace preferences.</McDrawerDescription>
        </McDrawerHeader>
      </McDrawerContent>
    </McDrawer>
  );
}

Examples

import { Moon } from 'lucide-react';
import {
  McDrawer,
  McDrawerContent,
  McDrawerNav,
  McDrawerNavItem,
  McDrawerTitle,
  McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export function NavigationDrawer() {
  return (
    <McDrawer direction="right">
      <McDrawerTrigger>Open navigation</McDrawerTrigger>
      <McDrawerContent>
        <McDrawerTitle className="sr-only">Navigation</McDrawerTitle>
        <McDrawerNav />
        <div className="flex flex-1 flex-col overflow-y-auto pt-9">
          <McDrawerNavItem icon={<Moon size={16} />} hasArrow>
            Account
          </McDrawerNavItem>
          <McDrawerNavItem icon={<Moon size={16} />} hasArrow>
            Settings
          </McDrawerNavItem>
        </div>
      </McDrawerContent>
    </McDrawer>
  );
}

Bottom Sheet

import {
  McDrawer,
  McDrawerClose,
  McDrawerContent,
  McDrawerDescription,
  McDrawerHeader,
  McDrawerTitle,
  McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export function ProfileDrawer() {
  return (
    <McDrawer direction="bottom">
      <McDrawerTrigger>Edit profile</McDrawerTrigger>
      <McDrawerContent>
        <div className="flex flex-col gap-6 px-4 py-6">
          <McDrawerHeader className="gap-2 p-0">
            <McDrawerTitle>Edit profile</McDrawerTitle>
            <McDrawerDescription>Save your changes before closing the drawer.</McDrawerDescription>
          </McDrawerHeader>

          <input
            type="email"
            defaultValue="m@example.com"
            className="w-full rounded-lg border border-border bg-input px-4 py-3 text-sm"
          />

          <McDrawerClose className="w-full rounded-lg bg-primary py-4 text-sm font-medium text-primary-foreground">
            Save changes
          </McDrawerClose>
        </div>
      </McDrawerContent>
    </McDrawer>
  );
}

API Reference

Root Components

ComponentDescription
McDrawerRoot wrapper that manages drawer state and direction
McDrawerTriggerElement that opens the drawer
McDrawerContentDrawer panel rendered in a portal with overlay support
McDrawerPortalPortal wrapper from the underlying drawer primitive
McDrawerOverlayFixed overlay displayed behind the drawer content
McDrawerCloseElement that closes the drawer

Layout Components

ComponentDescription
McDrawerHeaderHeader region for titles and descriptions
McDrawerFooterFooter region for actions
McDrawerTitleAccessible drawer title
McDrawerDescriptionAccessible supporting description
McDrawerNavTop navigation row with a built-in close control
McDrawerNavItemFull-width drawer navigation item

Common Props

McDrawer

PropTypeDefaultDescription
direction"top" | "right" | "bottom" | "left""bottom"Side where the drawer opens
openbooleanundefinedControlled open state
onOpenChange(open: boolean) => voidundefinedCallback fired when open state changes

McDrawerContent

PropTypeDefaultDescription
classNamestringundefinedAdditional classes for the panel
childrenReactNodeundefinedContent rendered inside the drawer

McDrawerNavItem

PropTypeDefaultDescription
iconReactNodeundefinedOptional leading icon
hasArrowbooleanfalseShows a trailing chevron
rightElementReactNodeundefinedCustom trailing element, such as a switch
classNamestringundefinedAdditional classes for the item

All other relevant props from vaul are supported on their matching wrappers.

On this page