Dropdown

A dropdown menu component for displaying a list of actions or options. Supports keyboard navigation and outside click handling.

Installation

Terminal
ReactTailwind
TSX
npx @uiblox/cli add dropdown

Visual Variations

Toggle between three different visual styles: default (shadow), minimal (flat), and bordered (thick border).

Basic menu items

Items with icons

Grouped sections

Basic Usage

Copy & paste ready
<Dropdown>
  <DropdownTrigger className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium border border-slate-300 dark:border-slate-700 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800">
    Open Menu
  </DropdownTrigger>
  <DropdownContent>
    <DropdownItem>Profile</DropdownItem>
    <DropdownItem>Settings</DropdownItem>
    <DropdownSeparator />
    <DropdownItem>Logout</DropdownItem>
  </DropdownContent>
</Dropdown>

With Labels and Groups

ReactTailwind CSSCopy & paste ready
<Dropdown>
  <DropdownTrigger className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium bg-purple-600 text-white rounded-md hover:bg-purple-700">
    Account
  </DropdownTrigger>
  <DropdownContent>
    <DropdownLabel>My Account</DropdownLabel>
    <DropdownItem>Profile</DropdownItem>
    <DropdownItem>Billing</DropdownItem>
    <DropdownSeparator />
    <DropdownLabel>Team</DropdownLabel>
    <DropdownItem>Invite Members</DropdownItem>
    <DropdownItem>Team Settings</DropdownItem>
  </DropdownContent>
</Dropdown>

Alignment

ReactTailwind CSSCopy & paste ready
<Dropdown>
  <DropdownTrigger className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium border border-slate-300 dark:border-slate-700 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800">
    Right Aligned
  </DropdownTrigger>
  <DropdownContent align="end">
    <DropdownItem>Option 1</DropdownItem>
    <DropdownItem>Option 2</DropdownItem>
  </DropdownContent>
</Dropdown>

Props

Dropdown

PropTypeDefaultDescription
children*React.ReactNode-The dropdown trigger and content
classNamestring-Additional CSS classes

DropdownContent

PropTypeDefaultDescription
align"start" | "center" | "end""start"Alignment of the dropdown content

Source Code

Copy this code into src/components/ui/dropdown.tsx:

dropdown.tsx
ReactTailwind
TSX
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

interface DropdownContextValue {
  open: boolean;
  setOpen: (open: boolean) => void;
}

const DropdownContext = React.createContext<DropdownContextValue | undefined>(undefined);

function useDropdownContext() {
  const context = React.useContext(DropdownContext);
  if (!context) {
    throw new Error("Dropdown components must be used within a Dropdown provider");
  }
  return context;
}

const Dropdown = ({ children, className }: { children: React.ReactNode; className?: string }) => {
  const [open, setOpen] = React.useState(false);
  const dropdownRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
        setOpen(false);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, []);

  return (
    <DropdownContext.Provider value={{ open, setOpen }}>
      <div ref={dropdownRef} className={cn("relative inline-block", className)}>
        {children}
      </div>
    </DropdownContext.Provider>
  );
};

const DropdownTrigger = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
  ({ className, children, ...props }, ref) => {
    const { open, setOpen } = useDropdownContext();
    return (
      <button ref={ref} onClick={() => setOpen(!open)} aria-expanded={open} className={className} {...props}>
        {children}
      </button>
    );
  }
);

const DropdownContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end" }>(
  ({ className, align = "start", ...props }, ref) => {
    const { open } = useDropdownContext();
    if (!open) return null;
    return (
      <div
        ref={ref}
        className={cn(
          "absolute z-50 mt-2 min-w-[8rem] rounded-md border bg-white dark:bg-gray-900 p-1 shadow-md",
          align === "start" && "left-0",
          align === "end" && "right-0",
          className
        )}
        {...props}
      />
    );
  }
);

const DropdownItem = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
  ({ className, ...props }, ref) => {
    const { setOpen } = useDropdownContext();
    return (
      <button
        ref={ref}
        className={cn(
          "flex w-full items-center rounded-sm px-2 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-800",
          className
        )}
        onClick={(e) => { props.onClick?.(e); setOpen(false); }}
        {...props}
      />
    );
  }
);

export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem };