# Team Grid Hover Bio — React Team Section

> A grid of drawn gradient portraits; hovering or focusing a person slides a frosted bio panel up over the portrait while the silhouette drifts back.

- Category: [Team & About](https://pixelpapa.com/components/team.md)
- Tags: team, grid, hover, bio, portrait
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/team/team-grid-hover-bio

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/team-grid-hover-bio.json"
```

The file is written to `components/pixelpapa/team-grid-hover-bio.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ArrowUpRight } from "lucide-react";
import { motion } from "motion/react";

type Member = { name: string; role: string; bio: string; based: string; tones: [string, string]; hair: number };

const defaultMembers: Member[] = [
  { name: "Ilse Vandermeer", role: "Co-founder & CEO", bio: "Ran logistics for a flower exporter before deciding the software was the real problem.", based: "Utrecht", tones: ["var(--chart-1)", "var(--chart-3)"], hair: 0 },
  { name: "Kofi Asante", role: "Co-founder & CTO", bio: "Wrote the first routing engine on a ferry with no wifi. Still prefers working offline.", based: "Accra", tones: ["var(--chart-2)", "var(--chart-1)"], hair: 1 },
  { name: "Noor Haddad", role: "Head of Design", bio: "Believes every screen should survive being read on a forklift at 6 am.", based: "Beirut", tones: ["var(--chart-4)", "var(--chart-3)"], hair: 2 },
  { name: "Tomás Rivas", role: "Staff Engineer", bio: "Keeps our map tiles under 40 ms and our incident channel boring.", based: "Valparaíso", tones: ["var(--chart-5)", "var(--chart-2)"], hair: 3 },
  { name: "Aiko Tanabe", role: "Customer Success", bio: "Has personally onboarded 212 warehouses and remembers all of their dock numbers.", based: "Osaka", tones: ["var(--chart-3)", "var(--chart-4)"], hair: 2 },
  { name: "Björn Sandvik", role: "Data Science", bio: "Forecasts arrival times and, less successfully, the office lunch order.", based: "Bergen", tones: ["var(--chart-1)", "var(--chart-5)"], hair: 3 },
  { name: "Leila Moradi", role: "Operations", bio: "Turned our finance close from nine days into two with a spreadsheet and patience.", based: "Toronto", tones: ["var(--chart-2)", "var(--chart-4)"], hair: 0 },
  { name: "Sam Whitlock", role: "Developer Relations", bio: "Writes the docs people actually finish. Hosts our monthly API clinic.", based: "Bristol", tones: ["var(--chart-5)", "var(--chart-3)"], hair: 1 },
];

// Four hair shapes drawn over the same head so portraits do not all look alike.
const hairPaths = [
  "M31 40c-3-16 6-27 19-27s23 10 19 28c-2-9-8-14-19-14s-17 5-19 13Z",
  "M32 34c0-12 8-20 18-20s18 8 18 20c-5-7-11-10-18-10s-13 3-18 10Z",
  "M29 62c-5-26 4-49 21-49s27 22 21 49c-1-16-4-34-21-34S31 46 29 62Z",
  "M33 32c1-10 8-17 17-17 10 0 17 7 17 17-3-4-9-7-17-7s-14 3-17 7Z",
];

function Portrait({ tones, hair }: Pick<Member, "tones" | "hair">) {
  return (
    <svg viewBox="0 0 100 125" className="size-full" preserveAspectRatio="xMidYMax slice" aria-hidden>
      <circle cx="50" cy="40" r="17" fill="white" fillOpacity="0.32" />
      <path d={hairPaths[hair % hairPaths.length]} fill="black" fillOpacity="0.28" />
      <path d="M8 125c0-30 18-48 42-48s42 18 42 48Z" fill="white" fillOpacity="0.22" />
      <path d="M40 78c3 6 17 6 20 0" stroke={tones[1]} strokeWidth="1.5" strokeOpacity="0.5" fill="none" />
    </svg>
  );
}

export default function TeamGridHoverBio({ members = defaultMembers }: { members?: Member[] }) {
  return (
    <section className="w-full bg-background px-6 py-20">
      <div className="mx-auto max-w-6xl">
        <div className="flex flex-wrap items-end justify-between gap-6">
          <div className="max-w-xl">
            <p className="text-sm font-medium text-primary">The crew</p>
            <h2 className="mt-3 text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
              Thirty-one people, nine countries, zero middle managers.
            </h2>
          </div>
          <a href="#" className="group inline-flex items-center gap-1.5 text-sm font-medium text-foreground hover:text-primary">
            See open roles
            <ArrowUpRight className="size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
          </a>
        </div>

        <motion.ul
          initial="hidden"
          whileInView="shown"
          viewport={{ once: true, amount: 0.1 }}
          transition={{ staggerChildren: 0.06 }}
          className="mt-12 grid grid-cols-2 gap-x-5 gap-y-8 lg:grid-cols-4"
        >
          {members.map((m) => (
            <motion.li
              key={m.name}
              variants={{
                hidden: { opacity: 0, y: 24 },
                shown: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 140, damping: 20 } },
              }}
            >
              <div
                tabIndex={0}
                className="group relative aspect-[4/5] overflow-hidden rounded-3xl outline-offset-4"
                style={{ background: `linear-gradient(160deg, ${m.tones[0]}, ${m.tones[1]})` }}
              >
                <div
                  aria-hidden
                  className="absolute inset-0 opacity-40 mix-blend-overlay [background-image:radial-gradient(white_1px,transparent_1px)] [background-size:14px_14px]"
                />
                <div className="absolute inset-0 transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-110 group-hover:-translate-y-3 group-focus-visible:scale-110 group-focus-visible:-translate-y-3">
                  <Portrait tones={m.tones} hair={m.hair} />
                </div>
                <span className="absolute top-4 left-4 rounded-full bg-black/25 px-2.5 py-1 text-[11px] font-medium text-white backdrop-blur-sm">
                  {m.based}
                </span>
                <div className="absolute inset-x-2 bottom-2 translate-y-[calc(100%+0.5rem)] rounded-2xl bg-background/85 p-4 backdrop-blur-md transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-y-0 group-focus-visible:translate-y-0">
                  <p className="text-sm leading-relaxed text-foreground">{m.bio}</p>
                </div>
              </div>
              <h3 className="mt-4 font-medium text-foreground">{m.name}</h3>
              <p className="text-sm text-muted-foreground">{m.role}</p>
            </motion.li>
          ))}
        </motion.ul>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "Team Grid Hover Bio" component from PixelPapa UI to this project.

What it is: A grid of drawn gradient portraits; hovering or focusing a person slides a frosted bio panel up over the portrait while the silhouette drifts back.
Category: Team & About. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/team-grid-hover-bio.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/team-grid-hover-bio.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/team-grid-hover-bio" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More team & about

- [About Story Timeline](https://pixelpapa.com/components/team/about-story-timeline.md): Company milestones alternate either side of a centre line that fills as you scroll; each year lights its node and slides its card in from its own side.
- [Team Leadership Rows](https://pixelpapa.com/components/team/team-leadership-rows.md): Leadership as large typographic rows between hairlines; the hovered or focused row springs open to show a portrait, a quote and a few facts.
- [Team Values And Photos](https://pixelpapa.com/components/team/team-values-and-photos.md) (Pro): A numbered values list that advances on a timer beside a collage of gradient photo tiles; the tiles tied to the active value lift forward while the rest recede.
