A responsive grid of six contact cards, up to three columns, each with a greyscale Cloud shape avatar, name, role badge and title, and a ruled footer split into Email and Call links.
import { EnvelopeSimpleIcon, PhoneIcon } from "@phosphor-icons/react/dist/ssr"
import { cn } from "cn"
import {
CloudShape,
type CloudPalette,
} from "@/components/graphics/cloud-shapes"
import { Badge } from "@/components/ui/badge"
import { buttonVariants } from "@/components/ui/button"
import { Card, CardContent, CardFooter } from "@/components/ui/card"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { Separator } from "@/components/ui/separator"
// The copy: change text here, not in the markup.
const COPY = {
email: "Email",
call: "Call",
} as const
// Knob: the orb's shading; zero chroma keeps it greyscale.
const ORB: CloudPalette = {
main: "oklch(0.55 0 0)",
accent: "oklch(0.7 0 0)",
}
const PEOPLE = [
{
name: "JoAnn Morgan",
title: "Instrumentation Controller",
role: "Firing Room",
email: "joann@orbcore.co",
telephone: "+1-555-0141",
},
{
name: "Gene Kranz",
title: "Flight Director",
role: "Mission Control",
email: "gene@orbcore.co",
telephone: "+1-555-0142",
},
{
name: "Katherine Johnson",
title: "Trajectory Analyst",
role: "Flight Dynamics",
email: "katherine@orbcore.co",
telephone: "+1-555-0143",
},
{
name: "Margaret Hamilton",
title: "Flight Software Lead",
role: "Guidance Computer",
email: "margaret@orbcore.co",
telephone: "+1-555-0144",
},
{
name: "Günter Wendt",
title: "Pad Leader",
role: "Launch Pad",
email: "gunter@orbcore.co",
telephone: "+1-555-0145",
},
{
name: "Steve Bales",
title: "Guidance Officer",
role: "Mission Control",
email: "steve@orbcore.co",
telephone: "+1-555-0146",
},
] as const
// The two actions share the ruled footer at equal widths.
const ACTION = cn(
buttonVariants({ variant: "ghost" }),
"h-14 flex-1 rounded-none"
)
export default function ContactList() {
return (
<ul
role="list"
className="mx-auto grid w-full max-w-5xl gap-(--card-gap) sm:grid-cols-2 lg:grid-cols-3"
>
{PEOPLE.map((person) => (
<li key={person.email}>
<Card className="h-full gap-0 py-0">
<CardContent className="flex-1 py-6">
<Item className="p-0">
<ItemMedia>
<CloudShape palette={ORB} width={40} height={40} />
</ItemMedia>
<ItemContent className="gap-1">
<ItemTitle className="flex-wrap gap-x-2 gap-y-1 text-base">
<span>{person.name}</span>
<Badge variant="secondary">{person.role}</Badge>
</ItemTitle>
<ItemDescription className="text-sm">
{person.title}
</ItemDescription>
</ItemContent>
</Item>
</CardContent>
<Separator />
<CardFooter className="px-0">
<a href={`mailto:${person.email}`} className={ACTION}>
<EnvelopeSimpleIcon weight="light" aria-hidden="true" />
{COPY.email}
<span className="sr-only">, {person.name}</span>
</a>
<Separator orientation="vertical" />
<a href={`tel:${person.telephone}`} className={ACTION}>
<PhoneIcon weight="light" aria-hidden="true" />
{COPY.call}
<span className="sr-only">, {person.name}</span>
</a>
</CardFooter>
</Card>
</li>
))}
</ul>
)
}
pnpm dlx shadcn add @01orbital/contact-listnpx shadcn add @01orbital/contact-listyarn shadcn add @01orbital/contact-listbunx --bun shadcn add @01orbital/contact-listFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/blocks/app/contact-list/index.tsx, as source you own and edit.
import ContactList from "@/components/blocks/app/contact-list"
export default function Example() {
return <ContactList />
}npm: @phosphor-icons/react, cn. The CLI installs them for you.
Registry pieces it installs with it:
badgebuttoncarditemseparatorCommand Palette
A command palette in a card: a searchable list of works with thumbnails and an open marker, copy-link and open-full-size actions, and a footer of key hints and a live status in Geist Mono.
Contact List Card
A single card listing four on-call contacts as rows with a greyscale Cloud shape avatar, name, title and an outline Email button. Rows are divided by hairlines under a header and description.