Flip Portrait Tiles
A wall of square portrait tiles that flip over in 3D on hover or tap to reveal the quote on the back, with touch and keyboard support.
Show customer testimonials as a wall of square portrait tiles. The front is a gradient portrait placeholder generated from a hue (or your own photoSrc) with the name and title overlaid in the bottom-left corner; when the pointer enters, the tile flips over in 3D on a spring and the back shows the quote, company and star rating. The flip direction follows the edge the pointer entered from; on touch devices a tap flips the tile and another tap flips it back, and keyboard users toggle with Enter or Space. On entrance the tiles fade in staggered along the diagonal.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add flip-portrait-tiles.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FlipPortraitTiles/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/flip-portrait-tiles.en.jsonOr, once registries are configured in components.json, install it as @webberui/flip-portrait-tiles.
Usage
import { FlipPortraitTiles } from "@/components/ui/flip-portrait-tiles";
<FlipPortraitTiles
eyebrow="客戶回饋"
title="他們這樣說"
columns={4}
testimonials={[
{
quote: "改版後的訂位頁讓週末候位少了一半。",
highlight: "少了一半",
name: "林曉晴",
role: "店長",
company: "青嶼咖啡",
rating: 5,
},
{
quote: "司機端的簽收流程終於順了。",
name: "陳柏宇",
role: "營運主管",
company: "北辰物流",
rating: 4,
photoSrc: "/images/chen.jpg",
},
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
testimonials | FlipPortraitTilesItem[] | — | Testimonials to show (required) |
eyebrow | string | — | Small label above the title |
title | string | "客戶怎麼說" ("What our customers say") | Section title, also the section's accessible name |
subtitle | string | — | Supporting text below the title |
columns | 3 | 4 | 4 | Column count at desktop width; drops to 2–3 columns on narrow screens |
trigger | "hover" | "click" | "auto" | "auto" | Trigger; auto uses hover for fine pointers (mouse) and tap for touch |
flipDirection | "auto" | "horizontal" | "vertical" | "auto" | Flip direction; auto follows the edge the pointer entered from |
stiffness | number | — | Flip spring stiffness; reads --wb-flip-portrait-tiles-stiffness when omitted, then falls back to 220 |
value | number[] | — | Controlled: indices of the flipped tiles |
defaultValue | number[] | [] | Initially flipped indices in uncontrolled mode |
onChange | (value: number[]) => void | — | Fires when the flipped set changes, with the flipped indices in ascending order |
className | string | — | Merged into the root section |
FlipPortraitTilesItem
| Field | Type | Default | Description |
|---|---|---|---|
quote | string | — | Quote text; shrinks to small type past 120 characters |
name | string | — | Name |
role | string | — | Job title |
company | string | — | Company name, shown at the bottom of the back face |
rating | number | — | Star rating (0–5), rounded for display |
avatarHue | number | Derived from index | Hue (0–360) of the portrait placeholder; assigned by index with the 137.508° golden angle by default |
highlight | string | — | Fragment of the quote to mark in the accent color |
metric | { value: string; label: string } | — | Result metric shown at the top of the back face |
photoSrc | string | — | Portrait photo URL; replaces the gradient silhouette |
How it works
- Flip direction: with
flipDirection="auto"the edge nearest to where the pointer entered decides — enter from the left and it flips right, from the right it flips left, from the top it flips down, from the bottom it flips up; on touch the edge nearest to the press point decides. A flipped tile never changes axis, so it cannot jump mid-flip. - Trigger:
autochecks(pointer: coarse). SSR and the first render always use hover mode and switch after mount. In hover mode several tiles can be open at once; in tap mode each tile toggles on its own and a second tap flips it back. - Timing and color: spring damping is fixed at 22, stiffness comes from
stiffnessor--wb-flip-portrait-tiles-stiffness; the accent only flows through--wb-flip-portrait-tiles-accent(default#0d9488,#2dd4bfin dark mode) and is used for highlights, stars and the focus ring. - The portrait placeholder needs no image: a two-tone gradient from the hue plus an abstract bust silhouette. The hue is deterministic, so server and browser render the same thing.
Accessibility
- The root is a
sectionlabelled by its title viaaria-labelledby - Each tile's interaction layer is a
buttonwitharia-pressed(flipped or not) and anaria-labelof "查看 name 的評價" ("View name's testimonial" in the English build); Tab moves between tiles, Enter or Space flips - The quote is rendered as
figure/blockquote/figcaptionoutside the button: the back face isaria-hiddenwhile unflipped and readable once flipped; the stars carry hidden "Rated N out of 5" text - When the user has reduce motion enabled, there is no 3D rotation or scale entrance — flipping becomes a 150ms crossfade between front and back, and the entrance is a plain fade
Fanned Quote Deck
Testimonial cards fan out like a hand of playing cards; the hovered card lifts as its neighbours make room, and a click draws it to the centre enlarged for reading, with a button to deal the next.
Star-Rated Reviews
A row of rated testimonials. The overall score counts up at the top while each card's stars fill one by one with fractional ratings, alongside a product thumbnail and a verified-purchase badge.