Skip to content

Avatar Group

Round photos overlapping in a row with a count bubble; the one you point at rises.

$npx shadcn@latest add https://framebrick.co/r/avatar-group.json

From the Metuo template, see it in context.

  • 65+

Trusted by 65+ clients

Installation

$ npx shadcn@latest add https://framebrick.co/r/avatar-group.json

Usage

import { AvatarGroup } from "@/components/ui/avatar-group";

<AvatarGroup label="Trusted by more than 65 clients" more="65+">
  {clients.map((client) => (
    <img key={client.photo} src={client.photo} alt="" />
  ))}
</AvatarGroup>

Props

PropTypeDefaultDescription
childrenReactNode—The photos: an <img>, a next/image with fill, a link around one. Give each an alt text, or an empty one when the group is decoration and label says what it is.
labelstring—Accessible name of the group, e.g. "Trusted by 65+ clients".
moreReactNode—What the bubble after the photos says, e.g. "65+". Without it there is no bubble.
sizenumber42Diameter of a photo, in px.
overlapnumber15How far each photo overlaps the one before it, in px.
risenumber20How far the photo you point at rises, in px.
  • It is the Metuo "Client Ratings": four 42 px client photos overlapping in 17 px slots 10 px apart, then a "65+" bubble. Point at a photo and it rises 20 px while the one raised before comes down, at the same time; leaving the group lowers it. The bubble does not move. The rise is Framer's spring, bounce 0, 0.4 s (measured: 400.2 → 380.2 over about 0.44 s).
  • 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; a browser without linear() gets cubic-bezier(0.22, 1, 0.36, 1).
  • Each photo sits in a box the size of the photo, and it is the photo inside the box that rises, not the box: raise the box under the pointer and its lower edge slips out from under it, so it drops, comes back and flickers. The raised photo rises over what is above the group, so leave room there.
  • Hover only applies where the device can hover, and keyboard focus raises a photo that is a link. A rise is a movement, so it is off with reduced motion.
  • The demo's portraits are Unsplash photos used as placeholders. Use your own clients' photos: a stock portrait beside a count reads as that person's endorsement.

AI prompt

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

Create a React + Tailwind CSS component called AvatarGroup — plain CSS, no JavaScript and no animation library, so it works in a server component.
- Props: children (the photos), label (aria-label of the list), more (the bubble's content), size (42), overlap (15), rise (20), className and the ul attributes. Set --size, --overlap and --rise-by inline.
- A ul (flex items-center, [--rise:var(--rise-by)] and motion-reduce:[--rise:0px]) with one li per child: size-(--size) shrink-0 relative, -ml-(--overlap) except the first, its child size-full rounded-full object-cover. A last li, if more is given: the same size, rounded-full bg-primary text-primary-foreground text-sm font-medium, centred, with the same negative margin.
- The photo you point at rises: [@media(hover:hover)]:hover:[&>*]:-translate-y-(--rise), and has-[:focus-visible]:[&>*]:-translate-y-(--rise) for a link. Only the li's child translates (transition-[translate] over 400ms), never the li, so the pointer's footing is kept.
- The easing is a CSS custom property --avatar-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.4s inside supports-[transition-timing-function:linear(0,1)].

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