Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ainergiz avatar

Dropdown Menu

  • 6 installs
  • 6 repo stars
  • Updated February 8, 2026
  • ainergiz/design-inspirations

dropdown-menu is a Claude Code skill that builds React dropdown menus with correct click-outside detection and z-index stacking for card and list contexts.

About

dropdown-menu is a Claude Code skill that builds React dropdown menus which work correctly inside cards and list items. It addresses three common bugs: overflow clipping, sibling cards covering the menu, and double-toggle on the trigger click. A developer uses it when adding action, context, or overflow menus to list-based UI.

  • Fixes clipping, sibling z-index stacking, and double-toggle dropdown bugs
  • Uses click (not mousedown) for click-outside so stopPropagation works
  • Elevates the parent card z-index when the menu is open

Dropdown Menu by the numbers

  • 6 all-time installs (skills.sh)
  • Ranked #1,778 of 2,244 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

dropdown-menu capabilities & compatibility

Capabilities
dropdown menu · click outside · z index stacking
Use cases
frontend · ui design
Pricing
Free
From the docs

What dropdown-menu says it does

Creates dropdown menus with proper click-outside detection and z-index stacking for list contexts.
SKILL.md
Dropdown menus in list items have three common bugs:
SKILL.md
npx skills add https://github.com/ainergiz/design-inspirations --skill dropdown-menu

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs6
repo stars6
Last updatedFebruary 8, 2026
Repositoryainergiz/design-inspirations

What it does

Building React dropdown menus that handle z-index stacking and click-outside dismissal correctly in card and list contexts.

Who is it for?

Action or context menus inside cards and list items where z-index and click-outside behavior break.

Skip if: Full accessible combobox or select-widget implementations.

When should I use this skill?

When building action menus, context menus, or any dropdown that appears in cards or list items.

What you get

A dropdown that dismisses on outside click, stacks above siblings, and does not double-toggle.

  • DropdownMenu component with click-outside handling
  • parent card z-index elevation pattern

By the numbers

  • Solves 3 common dropdown bugs
  • 4 key implementation elements

Files

SKILL.mdMarkdownGitHub ↗

Dropdown Menu Pattern

Build dropdown menus that work correctly in list/card contexts, handling z-index stacking and click-outside dismissal properly.

Why This Pattern?

Dropdown menus in list items have three common bugs:

1. Clipped by parent's `overflow-hidden` - dropdown gets cut off 2. Covered by sibling cards - z-index doesn't help across stacking contexts 3. Double-toggle on trigger click - menu closes then reopens immediately

This pattern solves all three.

Core Implementation

"use client";

import { useState, useRef, useEffect } from "react";
import { MoreVertical, Pause, X } from "lucide-react";

// The dropdown menu component
function DropdownMenu({
  dark = false,
  onClose,
}: {
  dark?: boolean;
  onClose: () => void;
}) {
  const menuRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    function handleClickOutside(event: MouseEvent) {
      if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
        onClose();
      }
    }
    // IMPORTANT: Use "click" not "mousedown" to allow stopPropagation on trigger
    document.addEventListener("click", handleClickOutside);
    return () => document.removeEventListener("click", handleClickOutside);
  }, [onClose]);

  return (
    <div
      ref={menuRef}
      className={`absolute right-0 top-full mt-1 z-20 rounded-lg shadow-lg border overflow-hidden ${
        dark ? "bg-zinc-800 border-zinc-700" : "bg-white border-zinc-200"
      }`}
    >
      <button
        className={`flex items-center gap-2 w-full px-3 py-2 text-xs font-medium transition-colors ${
          dark
            ? "text-zinc-300 hover:bg-zinc-700"
            : "text-zinc-700 hover:bg-zinc-50"
        }`}
        onClick={(e) => {
          e.stopPropagation();
          onClose();
        }}
      >
        <Pause className="w-3.5 h-3.5" strokeWidth={1.5} />
        Pause
      </button>
      <button
        className={`flex items-center gap-2 w-full px-3 py-2 text-xs font-medium transition-colors ${
          dark ? "text-red-400 hover:bg-zinc-700" : "text-red-600 hover:bg-red-50"
        }`}
        onClick={(e) => {
          e.stopPropagation();
          onClose();
        }}
      >
        <X className="w-3.5 h-3.5" strokeWidth={1.5} />
        Cancel
      </button>
    </div>
  );
}

Key Elements

1. Click-Outside Detection (Use click, NOT mousedown)

// CORRECT - allows stopPropagation on trigger button
document.addEventListener("click", handleClickOutside);

// WRONG - fires before button's onClick, causing double-toggle
document.addEventListener("mousedown", handleClickOutside);

Why? With mousedown, the sequence is: 1. mousedown fires → click-outside closes menu 2. click fires on button → toggle reopens menu

With click, stopPropagation() on the button prevents the document listener from firing.

2. Parent Card Z-Index Elevation

When menu is open, elevate the entire parent card above siblings:

<div
  className={`rounded-xl border cursor-pointer relative ${
    menuOpen ? "z-30" : "z-0"
  }`}
>
  {/* card content with dropdown inside */}
</div>

Why? Each card creates its own stacking context. The dropdown's z-20 only applies within its card. Sibling cards rendered later in the DOM naturally stack on top.

3. Avoid overflow-hidden on Dropdown Containers

// BAD - clips dropdown regardless of z-index
<div className="rounded-xl overflow-hidden">
  <DropdownMenu />
</div>

// GOOD - only use overflow-hidden where needed (e.g., expandable sections)
<div className="rounded-xl">
  <div className="relative">
    <DropdownMenu />
  </div>
  <div className="overflow-hidden">
    {/* expandable content only */}
  </div>
</div>

4. Trigger Button with stopPropagation

<div className="relative">
  <button
    onClick={(e) => {
      e.stopPropagation(); // Prevents parent card click AND click-outside
      onMenuToggle?.();
    }}
    className="p-1.5 -m-1.5 rounded-lg hover:bg-zinc-100 transition-colors cursor-pointer"
  >
    <MoreVertical className="w-5 h-5 text-zinc-400" strokeWidth={1.5} />
  </button>
  {menuOpen && onMenuClose && <DropdownMenu onClose={onMenuClose} />}
</div>

Note the -m-1.5 negative margin - this increases the clickable area without affecting layout.

Full Card Example with Dropdown

interface CardProps {
  title: string;
  menuOpen?: boolean;
  onMenuToggle?: () => void;
  onMenuClose?: () => void;
}

function Card({ title, menuOpen = false, onMenuToggle, onMenuClose }: CardProps) {
  return (
    <div
      className={`rounded-xl border border-zinc-200 p-4 cursor-pointer relative ${
        menuOpen ? "z-30" : "z-0"
      }`}
      onClick={() => console.log("card clicked")}
    >
      <div className="flex items-center justify-between">
        <span className="font-medium">{title}</span>
        <div className="relative">
          <button
            onClick={(e) => {
              e.stopPropagation();
              onMenuToggle?.();
            }}
            className="p-1.5 -m-1.5 rounded-lg hover:bg-zinc-100 transition-colors"
          >
            <MoreVertical className="w-5 h-5 text-zinc-400" strokeWidth={1.5} />
          </button>
          {menuOpen && onMenuClose && <DropdownMenu onClose={onMenuClose} />}
        </div>
      </div>
    </div>
  );
}

// Parent component managing which menu is open
function CardList() {
  const [openMenu, setOpenMenu] = useState<number | null>(null);
  const items = ["Item 1", "Item 2", "Item 3"];

  return (
    <div className="flex flex-col gap-3">
      {items.map((item, index) => (
        <Card
          key={index}
          title={item}
          menuOpen={openMenu === index}
          onMenuToggle={() => setOpenMenu(openMenu === index ? null : index)}
          onMenuClose={() => setOpenMenu(null)}
        />
      ))}
    </div>
  );
}

Menu Positioning Options

// Below, right-aligned (default)
className="absolute right-0 top-full mt-1"

// Below, left-aligned
className="absolute left-0 top-full mt-1"

// Above, right-aligned
className="absolute right-0 bottom-full mb-1"

// Above, left-aligned
className="absolute left-0 bottom-full mb-1"

Related: Tooltips in Stacked Items

When showing tooltips on items that have varying z-indexes (like stacked cards), the tooltip will be trapped in its parent's stacking context. The solution is to render the tooltip outside the item loop as a sibling element, calculating its position based on which item is hovered.

See the stacked-cards skill for the full pattern.

// WRONG - Tooltip trapped in parent's z-index
{items.map((item, i) => (
  <div style={{ zIndex: items.length - i }}>
    <Card />
    {hovered === i && <Tooltip />}  {/* Trapped! */}
  </div>
))}

// CORRECT - Tooltip outside the loop
{items.map((item, i) => (
  <div style={{ zIndex: items.length - i }}>
    <Card />
  </div>
))}
{hovered !== null && (
  <Tooltip style={{ /* calculated position */ }} />
)}

Checklist

  • [ ] Click-outside uses click event (not mousedown)
  • [ ] Parent card has conditional z-30 when menu is open
  • [ ] No overflow-hidden on containers that hold the dropdown
  • [ ] Trigger button has stopPropagation() in onClick
  • [ ] Menu items have stopPropagation() in onClick
  • [ ] Trigger wrapper has relative positioning
  • [ ] Dropdown has absolute positioning with top-full or bottom-full
  • [ ] For stacked items, tooltip rendered outside the loop (see stacked-cards skill)

Related skills

FAQ

What three bugs does dropdown-menu fix?

Clipping by overflow-hidden, sibling cards covering the menu, and double-toggle on the trigger click.

Why use click instead of mousedown?

click allows stopPropagation on the trigger button so the click-outside handler does not reopen the menu.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.