# Dropdown Menus on Mousedown

**SHOULD** · **ID:** `interactions-mousedown-dropdown` · **Category:** interactions
**Source:** [Rauno](https://interfaces.rauno.me/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-mousedown-dropdown

> SHOULD: Open dropdown menus on `mousedown`, not `click` — `click` fires on mouseup and adds perceived latency to the press.

Dropdown menus should trigger on mousedown for immediate response, not on click

The click event fires on mouseup — after the button is released. For dropdown menus that should feel instant, triggering on mousedown removes the perceived delay between pressing and seeing the menu appear.

## Bad — do not do this

`interactions-mousedown-dropdown-bad`

```tsx
import { useState } from 'react';

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

  return (
    <div className="w-full max-w-sm space-y-4">
      <div className="bg-card border border-border rounded-lg p-4">
        <div className="relative">
          <button
            onClick={() => setOpen(!open)}
            className="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm"
          >
            Options ▾
          </button>
          {open && (
            <div className="absolute top-full mt-1 left-0 bg-card border border-border rounded-lg shadow-lg py-1 min-w-[160px] z-10">
              <button className="w-full text-left px-3 py-1.5 text-sm text-foreground hover:bg-muted" onClick={() => setOpen(false)}>Edit</button>
              <button className="w-full text-left px-3 py-1.5 text-sm text-foreground hover:bg-muted" onClick={() => setOpen(false)}>Duplicate</button>
              <button className="w-full text-left px-3 py-1.5 text-sm text-destructive hover:bg-muted" onClick={() => setOpen(false)}>Delete</button>
            </div>
          )}
        </div>
        <p className="text-xs text-muted-foreground mt-3">Opens on click (mouseup) — slight perceived delay</p>
      </div>
      <p className="text-xs text-error">onClick triggers on mouseup — feels sluggish for menus</p>
    </div>
  );
}
```

## Good — do this

`interactions-mousedown-dropdown-good`

```tsx
import { useState, useEffect, useRef } from 'react';

export function MousedownDropdownGood() {
  const [open, setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!open) return;
    const handleClickOutside = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [open]);

  return (
    <div className="w-full max-w-sm space-y-4">
      <div className="bg-card border border-border rounded-lg p-4">
        <div className="relative" ref={ref}>
          <button
            onMouseDown={() => setOpen(!open)}
            className="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm"
          >
            Options ▾
          </button>
          {open && (
            <div className="absolute top-full mt-1 left-0 bg-card border border-border rounded-lg shadow-lg py-1 min-w-[160px] z-10">
              <button className="w-full text-left px-3 py-1.5 text-sm text-foreground hover:bg-muted" onMouseDown={() => setOpen(false)}>Edit</button>
              <button className="w-full text-left px-3 py-1.5 text-sm text-foreground hover:bg-muted" onMouseDown={() => setOpen(false)}>Duplicate</button>
              <button className="w-full text-left px-3 py-1.5 text-sm text-destructive hover:bg-muted" onMouseDown={() => setOpen(false)}>Delete</button>
            </div>
          )}
        </div>
        <p className="text-xs text-muted-foreground mt-3">Opens on mousedown — feels instant</p>
      </div>
      <p className="text-xs text-success">onMouseDown opens immediately — snappier interaction</p>
    </div>
  );
}
```

## References

- [interfaces.rauno.me](https://interfaces.rauno.me/)
