卡片與網格

Flip Reveal Card

3D 翻面卡片,繞 X/Y 軸翻轉揭示正反兩面內容,支援懸停、點擊與完全受控。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add flip-reveal-card

還沒有專案,或安裝遇到問題?看快速開始 →

Flip Reveal Card 是用 React、Tailwind CSS 與 Motion 做的 3D 翻轉卡片(flip card)元件:正反兩面以 CSS 3D transform 疊在同一格,可設定懸停、點擊或完全受控翻面,翻轉軸可選左右或上下。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

1000
0.6
編輯中的呼叫預設屬性
<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型別預設值說明
childrenReact.ReactNode—放入 FlipRevealFront 與 FlipRevealBack
trigger"hover" | "click" | "none""hover"觸發方式:懸停/聚焦、點擊切換、完全受控
axis"x" | "y""y"翻轉軸:y 左右翻、x 上下翻
flippedboolean—受控狀態;提供時停用內建狀態
defaultFlippedbooleanfalse非受控模式的初始翻面狀態
onFlippedChange(flipped: boolean) => void—翻面狀態變更時觸發
perspectivenumber10003D 透視距離(px),越小立體感越強
durationnumber0.6翻面動畫時長(秒)
classNamestring—附加到最外層容器(常用來設定寬高)

同時支援其餘標準 div 屬性(如 aria-label)。

FlipRevealFront / FlipRevealBack

Prop型別預設值說明
childrenReact.ReactNode—該面的內容
classNamestring—覆寫預設卡面外觀(邊框、背景、內距等)

兩者皆繼承標準 div 屬性。

細節

  • 兩面以 CSS Grid 疊在同一格,並用 backface-visibility: hidden 隱藏背對觀者的一面;旋轉容器帶 transform-style: preserve-3d,切勿在其上加 overflow-hidden(會攤平 3D)。
  • 卡面本身可自由設定 overflow-hidden 與圓角,因為它只是平面內容層。
  • axis 切換時,背面會自動改用對應軸向的 180° 預旋轉,正反面始終共面對齊。

可及性

  • 使用者系統開啟「減少動態效果」時,翻面改為瞬間切換(duration 0),不做旋轉動畫,也不改變 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 的話可從安裝區塊的「手動」分頁複製原始碼。