Frontend Lab
← 回到所有模式
PB07🪟 Overlay · 覆蓋層選單

Dropdown Menu

下拉選單

難度 ★★★★★標籤 dropdown · menu · 鍵盤3 min read

模式簡述

Dropdown:按鈕點開後跟著的選單。和 popover 像但通常只有列表,並支援鍵盤上下選擇。

關閉|預設關閉

Claude Code Prompt

💬 Claude Code Prompt
參考:PB07 dropdown
組件:Dropdown.tsx

Dropdown:按鈕點開後跟著的選單。和 popover 像但通常只有列表,並支援鍵盤上下選擇。

限制:
- 沒處理鍵盤導航
- 外點關閉但點按鈕也關閉
- 寬度跟 trigger 對不齊

完整程式碼

Dropdown.tsx
"use client";
import { useEffect, useState } from "react";
import { ChevronDown, User, Settings, LogOut, CreditCard } from "lucide-react";

type State = "closed" | "open" | "grouped";

export function Dropdown({ state = "open" }: { state?: State }) {
const [open, setOpen] = useState(state !== "closed");
useEffect(() => setOpen(state !== "closed"), [state]);
const grouped = state === "grouped";

return (
  <div className="grid h-[280px] w-full max-w-md place-items-center">
    <div className="relative">
      <button
        onClick={() => setOpen((o) => !o)}
        className="inline-flex items-center gap-1.5 rounded-md border border-border bg-background/50 px-3 py-1.5 text-xs hover:border-lab-accent"
      >
        帳戶 <ChevronDown className="h-3 w-3" />
      </button>
      {open && (
        <div className="absolute left-0 top-full z-10 mt-1 w-48 overflow-hidden rounded-md border border-border bg-background py-1 shadow-xl animate-in fade-in zoom-in-95 duration-100">
          {grouped && (
            <p className="px-2.5 py-1 font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70">
              帳戶
            </p>
          )}
          <button className="flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs hover:bg-lab-accent/10">
            <User className="h-3.5 w-3.5" /> 個人資料
          </button>
          <button className="flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs hover:bg-lab-accent/10">
            <CreditCard className="h-3.5 w-3.5" /> 帳單
          </button>
          <button className="flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs hover:bg-lab-accent/10">
            <Settings className="h-3.5 w-3.5" /> 設定
          </button>
          {grouped && (
            <>
              <div className="my-1 border-t border-border" />
              <p className="px-2.5 py-1 font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70">
                危險區
              </p>
            </>
          )}
          <button className="flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs text-red-500 hover:bg-red-500/10">
            <LogOut className="h-3.5 w-3.5" /> 登出
          </button>
        </div>
      )}
    </div>
  </div>
);
}

何時用

  • ✅ 使用者頭像下拉(登出、設定)
  • ✅ 次要操作集合(more menu)
  • ✅ 排序、篩選的選項列表

何時別用

  • ❌ 選項超過 8 個(用 combobox + search)
  • ❌ 需要多選(用 multi-select)
  • ❌ 選項需要圖示和描述(用 mega menu)

我踩過的坑

  1. 沒處理鍵盤導航:tab 直接跳過所有選項。要綁 ArrowUp/Down 切 focus。

  2. 外點關閉但點按鈕也關閉:點按鈕應該 toggle,外點才是 close。注意 e.stopPropagation。

  3. 寬度跟 trigger 對不齊:用 floating-ui 的 size middleware 抓 anchor 寬。

相關模式

  • PB05
  • PE01

搜尋

按 ⌘K 隨時開啟