# Contact List

> 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.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/contact-list).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/contact-list
- Screenshot: https://shadcn.exchange/shot/01orbital/contact-list/light.png
- Registry item: https://01orbital.dev/r/contact-list.json
- Categories: app
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/contact-list.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@phosphor-icons/react`, `cn`
- Registry items: `@01orbital/badge`, `@01orbital/button`, `card`, `@01orbital/cloud-shapes`, `@01orbital/item`, `@01orbital/separator`

## Files

### `blocks/app/contact-list/index.tsx`

```tsx
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>
  )
}
```
