Flip Reveal Card
3D 翻面卡片,繞 X/Y 軸翻轉揭示正反兩面內容,支援懸停、點擊與完全受控。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add flip-reveal-cardFlip Reveal Card 是用 React、Tailwind CSS 與 Motion 做的 3D 翻轉卡片(flip card)元件:正反兩面以 CSS 3D transform 疊在同一格,可設定懸停、點擊或完全受控翻面,翻轉軸可選左右或上下。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<FlipRevealCard/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 團隊介紹頁:正面放照片與職稱,背面放簡介與聯絡方式。
- 問答卡、單字卡或測驗題:點一下翻面看答案。
- 功能或方案特色卡:正面一句重點,背面列出細節。
- 活動頁的抽獎、優惠揭曉效果,由程式控制在指定時機翻面。
- 不適合:背面內容多到需要完整版面時,改用點擊後放大成詳情面板的 Morph Expand Card。
安裝
npx webberui@latest add https://webberui.com/r/flip-reveal-card.json或在 components.json 設定 registries 後,改用 @webberui/flip-reveal-card 安裝。
使用
import {
FlipRevealCard,
FlipRevealFront,
FlipRevealBack,
} from "@/components/ui/flip-reveal-card";
<FlipRevealCard trigger="hover" axis="y" className="h-40 w-64">
<FlipRevealFront>
<h3 className="text-base font-semibold">正面</h3>
</FlipRevealFront>
<FlipRevealBack>
<p className="text-sm">背面內容,翻過來才看得到。</p>
</FlipRevealBack>
</FlipRevealCard>兩面會自動疊放在同一格,卡片高度取兩面內容較高者;也可直接在 FlipRevealCard 上用 className 指定固定高度(如 h-40)。
受控模式
傳入 flipped 即進入受控模式,搭配 trigger="none" 可完全由外部控制翻面:
const [flipped, setFlipped] = React.useState(false);
<>
<FlipRevealCard trigger="none" flipped={flipped} className="h-40 w-64">
<FlipRevealFront>正面</FlipRevealFront>
<FlipRevealBack>背面</FlipRevealBack>
</FlipRevealCard>
<button onClick={() => setFlipped((v) => !v)}>翻面</button>
</>範例
團隊成員卡
預設 trigger="hover",滑鼠懸停或鍵盤聚焦時翻到背面。背面放連結也可以,焦點移到背面的連結時卡片不會翻回正面;記得用 aria-label 為卡片命名。
import {
FlipRevealCard,
FlipRevealFront,
FlipRevealBack,
} from "@/components/ui/flip-reveal-card";
<FlipRevealCard aria-label="王小明,前端工程師" className="h-56 w-48">
<FlipRevealFront className="items-center justify-center gap-2">
<img src="/team/ming.jpg" alt="" className="size-20 rounded-full object-cover" />
<p className="font-semibold">王小明</p>
<p className="text-sm text-neutral-500">前端工程師</p>
</FlipRevealFront>
<FlipRevealBack className="justify-between">
<p className="text-sm">負責設計系統與元件庫維護,熟悉 React 與無障礙規範。</p>
<a href="mailto:ming@example.com" className="text-sm underline">
寄信聯絡
</a>
</FlipRevealBack>
</FlipRevealCard>點擊翻面的問答卡
trigger="click" 讓卡片變成可聚焦的按鈕,點擊或按 Enter/空白鍵切換;axis="x" 改成上下翻。onFlippedChange 可以用來記錄使用者是否看過答案。
"use client";
import * as React from "react";
import {
FlipRevealCard,
FlipRevealFront,
FlipRevealBack,
} from "@/components/ui/flip-reveal-card";
export function QuizCard() {
const [revealed, setRevealed] = React.useState(false);
return (
<div className="space-y-2">
<FlipRevealCard
trigger="click"
axis="x"
onFlippedChange={(flipped) => flipped && setRevealed(true)}
className="h-40 w-72"
>
<FlipRevealFront className="justify-center">
<p className="font-medium">HTTP 狀態碼 404 代表什麼?</p>
</FlipRevealFront>
<FlipRevealBack className="justify-center">
<p>伺服器找不到請求的資源(Not Found)。</p>
</FlipRevealBack>
</FlipRevealCard>
{revealed && <p className="text-sm text-neutral-500">已查看答案</p>}
</div>
);
}自訂卡面與翻轉手感
兩面的 className 可以覆寫預設的邊框、背景與內距;perspective 越小立體感越強,duration 控制翻面秒數。
<FlipRevealCard perspective={600} duration={0.8} className="h-48 w-64">
<FlipRevealFront className="border-0 bg-neutral-900 text-white">
<p className="text-lg font-semibold">每月流量報告</p>
</FlipRevealFront>
<FlipRevealBack className="border-0 bg-amber-100 text-amber-950">
<p className="text-sm">自動彙整造訪數、跳出率與轉換率,每月 1 日寄到信箱。</p>
</FlipRevealBack>
</FlipRevealCard>Props
FlipRevealCard
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 放入 FlipRevealFront 與 FlipRevealBack |
trigger | "hover" | "click" | "none" | "hover" | 觸發方式:懸停/聚焦、點擊切換、完全受控 |
axis | "x" | "y" | "y" | 翻轉軸:y 左右翻、x 上下翻 |
flipped | boolean | — | 受控狀態;提供時停用內建狀態 |
defaultFlipped | boolean | false | 非受控模式的初始翻面狀態 |
onFlippedChange | (flipped: boolean) => void | — | 翻面狀態變更時觸發 |
perspective | number | 1000 | 3D 透視距離(px),越小立體感越強 |
duration | number | 0.6 | 翻面動畫時長(秒) |
className | string | — | 附加到最外層容器(常用來設定寬高) |
同時支援其餘標準 div 屬性(如 aria-label)。
FlipRevealFront / FlipRevealBack
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 該面的內容 |
className | string | — | 覆寫預設卡面外觀(邊框、背景、內距等) |
兩者皆繼承標準 div 屬性。
細節
- 兩面以 CSS Grid 疊在同一格,並用
backface-visibility: hidden隱藏背對觀者的一面;旋轉容器帶transform-style: preserve-3d,切勿在其上加overflow-hidden(會攤平 3D)。 - 卡面本身可自由設定
overflow-hidden與圓角,因為它只是平面內容層。 axis切換時,背面會自動改用對應軸向的 180° 預旋轉,正反面始終共面對齊。
可及性
- 使用者系統開啟「減少動態效果」時,翻面改為瞬間切換(
duration0),不做旋轉動畫,也不改變 DOM 結構。 - 目前朝背面的一面會被標記
aria-hidden並加上inert,因此不會被螢幕報讀,其內的可聚焦元素也不進入 Tab 順序。 trigger="click"時容器為role="button"、可聚焦,並支援 Enter/空白鍵切換,aria-pressed反映當前面向。trigger="hover"時容器可聚焦,鍵盤聚焦即翻面(等同懸停),失焦翻回;建議透過aria-label為卡片命名。
常見問題
可以在 Next.js App Router 使用嗎?
可以。元件檔案開頭已有 "use client",在 Server Component 頁面直接 import FlipRevealCard、FlipRevealFront、FlipRevealBack 即可,兩面的內容也可以在伺服器端渲染。
卡片高度怎麼決定?
兩面以 CSS Grid 疊在同一格,卡片高度取兩面中較高的一面。需要固定尺寸時,在 FlipRevealCard 的 className 設定寬高,例如 h-40 w-64。
螢幕報讀器會讀到兩面嗎?
不會。目前背對使用者的那一面會加上 aria-hidden 與 inert,不會被報讀,其中的連結或按鈕也不在 Tab 順序裡。
為什麼出現「必須放在 FlipRevealCard 內使用」的錯誤?
FlipRevealFront 與 FlipRevealBack 需要從 FlipRevealCard 取得翻面狀態,單獨使用時會丟出錯誤。請把兩者都放在同一個 FlipRevealCard 之內。
有支援減少動態效果嗎?
有。系統開啟「減少動態效果」時翻面改為瞬間切換,動畫時長視為 0,不做旋轉過程。依賴只有 motion、clsx 與 tailwind-merge,不用 CLI 的話可從安裝區塊的「手動」分頁複製原始碼。