People Grid
alphav0.2.0Responsive grid of person cards — avatar with initials fallback, name, title, per-card links, and a custom item renderer.
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
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/people-gridAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/people-grid-fixturesPreview
Konuşmacılar
Prof. Dr. Ahmet Yılmaz
Şehir Plancısı
Dr. Elif Kaya
Çevre Mühendisi
Mehmet Demir
Akıllı Şehir Uzmanı
Demo source
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">; sectionaria-labelledbyresolves to the heading id (viauseId) 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 toh3. - No
framedprop — 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