Skip to content
ilinxa/pro-ui

People Grid

alphav0.2.0

Responsive grid of person cards — avatar with initials fallback, name, title, per-card links, and a custom item renderer.

Category: Data DisplayUpdated: 2026-08-11Created: 2026-05-02Author: ilinxa

Context

Use for conference speakers, team / about-us pages, board / committee lists, contributor grids, podcast guests, course instructors, judge lineups. Migration origin: kasder kas-social-front-v0 events/[id]/page.tsx Konuşmacılar (Speakers) block. The `getInitials` helper is reusable for mention chips, comment headers, contact rows.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/people-grid

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/people-grid-fixtures

Preview

Konuşmacılar

  • Prof. Dr. Ahmet Yılmaz

    Prof. Dr. Ahmet Yılmaz

    Şehir Plancısı

  • Dr. Elif Kaya

    Dr. Elif Kaya

    Çevre Mühendisi

  • Mehmet Demir

    Mehmet Demir

    Akıllı Şehir Uzmanı

Demo source

demo.tsxtsx

Usage

When to use

Reach for PeopleGrid when you need a section heading + responsive grid of person cards (round avatar + name + title) — for conference speakers, team / about-us pages, board / committee lists, contributors, podcast guests, course instructors, judge lineups. Avatar gracefully falls back to initials when image is missing.

Minimal example

import { PeopleGrid } from "@/components/people-grid";

<PeopleGrid
  heading="Konuşmacılar"
  items={event.speakers.map((s, i) => ({
    id: String(i),
    name: s.name,
    title: s.title,
    image: s.image,
  }))}
  columns={3}
/>;

Public helper kernel — getInitials

The initials helper is exported as a pure function. Reuse it in mention chips, comment headers, contact rows, presence badges:

import { getInitials } from "@/components/people-grid";

getInitials("Dr. Ahmet Yılmaz")    // → "AY"
getInitials("Prof. Dr. Elif Kaya") // → "EK"
getInitials("Madonna")              // → "M"
getInitials("")                     // → "?"

// Common honorifics skipped: Dr., Prof., Mr., Mrs., Ms., Sr., Jr.

Columns × avatar size

All grids start at grid-cols-1 on mobile and scale up at sm/md/lg breakpoints based on columns:

  • columns=2 — 1 col mobile, 2 col sm:+
  • columns=3 — 1 col mobile, 2 col sm:, 3 col md:+
  • columns=4 — 1 col mobile, 2 col sm:, 4 col md:+
  • columns=5 — 1 col mobile, 2 col sm:, 3 col md:, 5 col lg:+

For columns: 4 or 5 in narrow containers, pair with avatarSize: "sm" or "md" — large avatars (w-24 h-24) at 4-5 columns need ~600px+ to render without crowding. Defaults (columns: 3, avatarSize: "lg") match kasder and work everywhere down to mobile.

Avatar fallback

Items without image render an initials circle with bg-primary/10 text-primary font-semibold. The component handles common honorifics (Dr./Prof./Mr./etc.) by skipping them when computing initials:

<PeopleGrid
  heading="Board of Directors"
  items={[
    { id: "1", name: "Dr. Sara Ahmed",      title: "Chair" },       // → "SA"
    { id: "2", name: "Prof. James O'Neill", title: "Vice-Chair" },   // → "JO"
    { id: "3", name: "Madonna",             title: "Director" },    // → "M"
  ]}
  // no image fields — all render with initials
/>

Polymorphic linkComponent

import NextLink from "next/link";

<PeopleGrid
  heading="Our Team"
  items={team.map((m) => ({ ...m, href: `/team/${m.slug}` }))}
  linkComponent={NextLink}
  columns={4}
/>

When hrefis supplied, the entire card becomes clickable via the overlay-link pattern. The link's accessible name = the person's name (via aria-labelledby) — screen reader announces just the name, not the flattened title/image-alt.

Custom renderItem

<PeopleGrid
  items={speakers}
  renderItem={(person) => (
    <div className="text-center">
      <Avatar src={person.image} />
      <h4 className="font-semibold">{person.name}</h4>
      <p className="text-sm text-muted-foreground">{person.title}</p>
      <SocialLinks links={person.social} />
    </div>
  )}
/>

Empty state

Empty items → renders emptyState if provided, else <p role="status"> with labels.emptyText(default "No people to display.").

Notes

  • Renders <ul role="list">; section aria-labelledby resolves to the heading id (via useId) when heading is supplied.
  • Heading defaults to h2 (people grids are top-level page sections — Speakers, Team, Contributors). Differs from sidebar info cards which default to h3.
  • No framed prop — people grids are typically standalone page sections, not nested cards. Wrap externally if you need card chrome.
  • Initials fallback is aria-hidden; the name in the adjacent <h4> is what screen readers announce.
  • Hover-color transition on the name is gated via motion-safe: — reduced-motion users see static text.

Features

  • Responsive grid — columns 2/3/4/5 with built-in breakpoint scaling (all start at 1 col mobile)
  • Avatar size variants (sm/md/lg)
  • Alignment (center/start)
  • Initials fallback when image is missing — handles Dr./Prof./etc. honorifics
  • Public getInitials helper kernel exported as pure function
  • Polymorphic per-card link via linkComponent + per-item href (overlay-link pattern)
  • Custom renderItem slot for full per-person takeover
  • Optional section heading with configurable level (h2/h3/h4, default h2)
  • Soft-failure on optional fields (title / image / imageAlt / href)
  • Empty state slot + labels.emptyText fallback
  • <ul role='list'> semantics + section aria-labelledby
  • aria-labelledby on per-card link → accessible name = person's name only

Tags

people-gridteamspeakersgridavatar

Dependencies