Skip to content

Photo Fan

Three photos tucked and tilted like a hand of cards, that straighten and spread when you point at them.

$npx shadcn@latest add https://framebrick.co/r/photo-fan.json

From the Kendi template, see it in context.

Installation

$ npx shadcn@latest add https://framebrick.co/r/photo-fan.json

Usage

import { PhotoFan } from "@/components/ui/photo-fan";

<PhotoFan
  label="Latest posts"
  items={[
    { href: "/blog/harbour", label: "Read: The quiet side of the harbour", media: <img src="/harbour.jpg" alt="" /> },
    { href: "/blog/room", label: "Read: A room that breathes", media: <img src="/room.jpg" alt="" /> },
    { href: "/blog/evenings", label: "Read: Evenings by the water", media: <img src="/sea.jpg" alt="" /> },
  ]}
/>

Props

PropTypeDefaultDescription
items[item, item, item]—Three photos, left, middle and right: { media, href?, label? }. media is an image with an empty alt text; href makes the photo a link and label names it.
labelstring—Accessible name of the group.
tiltnumber15How far the side photos are turned at rest, in degrees.
tucknumber40How far the side photos are pushed towards the middle at rest, in px.
classNamestring—Classes for the row (it is 625 px wide at most, centred).
  • It is Kendi's newsletter band ("Content/Image Variation"): three post photos in white frames (padding 5, corners 10), in a row at most 625 px wide, 10 px apart; the middle one is 33% of the row and in front, the two at the sides 25%, pushed 40 px in towards it and turned 15°, the left one anticlockwise. Pointing at the row straightens and spreads them to their places on Framer's slow spring (bounce 0, 0.6 s), and back when the pointer leaves.
  • Plain CSS: no animation library and no JavaScript, so it works as it is on a server page. The spring is a CSS linear() curve sampled from the spring itself, within 1% of it, over 600 ms (a browser without linear() gets cubic-bezier(0.22, 1, 0.36, 1)). The turn and the offset are CSS rotate and translate, so a test reads getComputedStyle(el).rotate and .translate, not transform. With reduced motion they change at once.
  • Hover only applies where the device can hover. The photos are decoration (alt=""): a photo that is a link is named by label, and keyboard focus on any of them also spreads the row.

AI prompt

Paste into v0, Lovable, Bolt or Claude to rebuild this component in your own project.

Create a React + Tailwind CSS component called PhotoFan — plain CSS, no animation library, no JavaScript.
- Props: items (three of { media, href?, label? }), label (aria-label of the group), tilt (degrees, 15), tuck (px, 40), className. A role="group" row (group/fan, mx-auto flex w-full max-w-[625px] items-center justify-center gap-2.5) with --fan-tilt and --fan-tuck set inline.
- Each photo is a frame (rounded-xl bg-card p-[5px] shadow-xs, a Next.js Link when it has an href, a div otherwise) holding a rounded-lg 4:3 box that stretches its image. The middle one is w-[33%] relative z-10; the sides are w-[25%]. At rest the left one is rotated by -tilt and translated by +tuck, the right one rotated by +tilt and translated by -tuck (the CSS rotate and translate properties); on group-hover and group-focus-within of the row they go to 0.
- transition-[rotate,translate] over 600ms with an easing from the custom property --fan-ease: cubic-bezier(0.22,1,0.36,1), replaced by a 30-stop linear() list sampled from a spring with bounce 0 and duration 0.6s inside supports-[transition-timing-function:linear(0,1)]; motion-reduce:transition-none.

Be the first to know

Get new templates and components first

One short email when we release a template or a free component. No spam — unsubscribe anytime.

We use your email only for Framebrick updates. Privacy policy