Frontend Lab
← 回到所有模式
PA02🧩 Compose · 互動組合折疊

Accordion

折疊面板

難度 ★★★★★標籤 折疊 · FAQ · 互斥 · 動畫高度3 min read

模式簡述

折疊面板看似簡單,雷區其實不少:高度動畫不能直接用 height: auto、互斥模式跟多開模式邏輯不同、 箭頭旋轉要跟內容同步。這裡用 grid-rows-[0fr→1fr] 技巧把高度動畫做得乾淨。

可以,MIT 授權。原作者保留標示權,但不限商用範圍。

全套支援。MDX 在 build 期靜態化,client 端只負責互動。

目前完全免費。未來可能推進階教學 / 一對一付費諮詢。

暫無計畫。React 生態為主,但 prompt 跨框架可移植。

全收合|預設狀態 — 全部關起

Claude Code Prompt

💬 Claude Code Prompt
參考:PA02 accordion
技術:純 React + Tailwind(grid-rows trick)

具體行為:
- 預設全部收合
- 點擊 header 展開內容,箭頭旋轉 180 度
- 高度動畫用 grid-rows-[0fr] → grid-rows-[1fr](不用 max-height hack)
- 多開模式:state 是 number[],可同時開多個
- 互斥模式:開新的關舊的,state 只保留一個

限制:
- 不要用 height: auto 過渡(CSS 不支援,會跳)
- 不要用 max-height: 9999px hack(duration 失準)
- 動畫 duration 0.3s 剛好,0.5 以上太拖
- 內容 overflow-hidden 必加,不然動畫期間會破版

完整程式碼

Accordion.tsx
'use client'

import { useState } from 'react'
import { ChevronDown } from 'lucide-react'

const ITEMS = [
{ q: '可以商業使用嗎?', a: '可以,MIT 授權。' },
{ q: '支援 SSR 嗎?', a: '全套支援。' },
]

export function Accordion({ exclusive = false }) {
const [open, setOpen] = useState<number[]>([])

function toggle(i: number) {
  if (exclusive) {
    setOpen(open.includes(i) ? [] : [i])
  } else {
    setOpen(open.includes(i) ? open.filter(x => x !== i) : [...open, i])
  }
}

return (
  <div>
    {ITEMS.map((item, i) => {
      const isOpen = open.includes(i)
      return (
        <div key={i}>
          <button onClick={() => toggle(i)}>
            {item.q}
            <ChevronDown className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
          </button>
          <div className={`grid transition-all duration-300 ${isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'}`}>
            <div className="overflow-hidden">
              <p>{item.a}</p>
            </div>
          </div>
        </div>
      )
    })}
  </div>
)
}

何時用

  • ✅ FAQ 區(內容多但不是每題都要讀)
  • ✅ 設定頁的分組(同時開多個可比對)
  • ✅ Sidebar 的多層導航

何時別用

  • ❌ 內容很短(< 2 行),全展開更省事
  • ❌ 內容必須讓使用者全部讀過(折疊會被忽略)
  • ❌ 表單分段(用 stepper 更清楚進度)

我踩過的坑

  1. height: auto 過渡無效:CSS 真的不支援 auto 到具體值的過渡。 早期用 max-height: 1000px,內容超過 1000 還是被截。最後改 grid-rows, 一次解決。

  2. 箭頭跟動畫不同步:箭頭 transform 用 100ms,內容高度用 300ms, 看起來像兩個元件。統一用 300ms 後順了。

  3. 互斥模式沒考慮「點同一個」:原本互斥邏輯只寫了「開新關舊」, 但點當前展開的也應該要收起。要先判斷是否是同一個。

相關模式

  • 暫無

搜尋

按 ⌘K 隨時開啟