# Contact card

> A React contact card: a person or company at a glance with avatar, role, status, call, email and WhatsApp buttons, tags and last activity.

- Type: React component (Data Display)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add contact-card`
- Docs: https://wingo-ui.com/components/contact-card

## Interaction

Tap Call, Email or WhatsApp to open the phone, the mail app or a WhatsApp chat (wa.me with the Romanian 40 prefix added to 07… numbers); an onCall / onEmail / onMessage handler replaces the link, for a softphone or an in-app composer, and the email and phone in the full card go through the same handlers. Tap the name (or anywhere on a linked card) to open the record: the card tints on hover and dips slightly while pressed (a row only tints), but neither dips nor darkens when the press lands on a button or link inside. The company, email, phone and website are underlined links of their own. The star saves the contact optimistically and rolls back, with a spoken error, when the request fails. The copy buttons next to email, phone and CUI copy the value and confirm with a check; they show on hover or keyboard focus of their row with a mouse and are always visible on touch. The ⋯ button opens the actions menu, a bottom sheet with the contact's name on phones. On narrow cards the buttons stack their icon over the label; in the row layout they appear on hover (always on touch), rows narrower than 30rem keep only the first one (Call), below 18rem none, and the row's ⋯ menu always lists every channel on top. Tab reaches the name link, the company link, the star, the menu, each contact button, then each field link and copy button, in reading order, with the focus ring drawn around the whole card for the name; Enter or Space activates; in the menu the arrows move and Escape closes it and returns focus to ⋯. Hover a round button for its label; long-press it on touch.

## Installation

```bash
npx wingo-ui@latest add contact-card
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/contact-card` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/contact-card.tsx`

npm dependencies: `npm i lucide-react motion`

Wingo UI dependencies (installed automatically):

- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Copy Button](https://wingo-ui.com/components/copy-button) ([Markdown](https://wingo-ui.com/components/copy-button.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Favorite button](https://wingo-ui.com/components/favorite-button) ([Markdown](https://wingo-ui.com/components/favorite-button.md))
- [Skeleton](https://wingo-ui.com/components/skeleton) ([Markdown](https://wingo-ui.com/components/skeleton.md))
- [Tag](https://wingo-ui.com/components/tag) ([Markdown](https://wingo-ui.com/components/tag.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { Pencil, PhoneCall, Trash2 } from "lucide-react"
import { ContactCard, type ContactData } from "@/components/ui/contact-card"

const emma: ContactData = {
  id: "c-emma",
  name: "Emma Carter",
  title: "Head of Procurement",
  company: { name: "Northwind Tech Inc.", href: "/clients/c01" },
  email: "emma.carter@northwindtech.com",
  phone: "+1 415 555 0132",
  whatsapp: true,
  location: "San Francisco, CA",
  status: { label: "Active client", tone: "success" },
  tags: [{ label: "Decision maker" }, { label: "2027 budget", tone: "info" }],
  owner: { name: "Anna Price", avatar: "/demo/ana-pop.jpg" },
  lastActivity: { label: "Call", at: new Date(2026, 8, 24, 15, 55), icon: <PhoneCall /> },
  href: "/contacts/emma-carter",
}

export function ContactPanel({ onFavorite }: { onFavorite: (favorite: boolean) => Promise<unknown> }) {
  return (
    <ContactCard
      contact={emma}
      defaultFavorite={false}
      onFavoriteChange={(favorite) => onFavorite(favorite)}
      actions={[
        { label: "Edit", icon: <Pencil /> },
        { type: "separator" },
        { label: "Delete contact", icon: <Trash2 />, tone: "danger" },
      ]}
      locale="en-US"
      labels={{ owner: "Account owner" }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `contact` | `ContactData` |  | { id; kind?: "person" \| "company"; name; avatar?; title?; company?: { name; href?; logo? }; email?; phone?; whatsapp?: string \| boolean (true uses phone); website?; location?; cui?; status?: { label; tone?; icon? }; tags?: { label; tone?; color? }[]; owner?: { name; avatar? }; lastActivity?: { label; at; icon? }; fields?: ContactFact[]; href? }. Anything missing simply does not render. |
| `layout?` | `"full" \| "compact" \| "row"` | `"full"` | full: every part, for a record's side panel. compact: centered avatar, name, role, status and round buttons, for grids. row: one 56px line with icon buttons, for lists. |
| `variant?` | `"elevated" \| "outline" \| "soft" \| "ghost"` | `"elevated"` | The card surface: a raised panel, a hairline frame, the flat gray tile or nothing (ghost, for rows inside a list group). |
| `size?` | `"sm" \| "md"` | `"md"` | md: a 48px avatar and a 17px name in the full layout (64px in compact); sm one step smaller. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Card corners: none, 12px, 16px or 24px. Rows inside a list group use none. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Colors the selected ring and the star when it is on. |
| `color?` | `string` | `""` | Any CSS color for the selected ring and the star. Replaces the tone. |
| `defaultFavorite?` | `boolean` | `false` | The star's starting state when uncontrolled. Setting it (or favorite, onFavoriteChange) shows the star. |
| `favorite?` | `boolean` |  | Controlled star. |
| `onFavoriteChange?` | `((favorite: boolean, contact: ContactData) => void \| Promise<unknown>)` |  | Called when the star is toggled. Return a promise to keep it optimistic while pending; a rejection rolls it back and announces labels.favoriteError. |
| `showFavorite?` | `boolean` | `false` | Shows the star even without favorite props (an uncontrolled star that starts off). |
| `selected?` | `boolean` | `false` | A 2px ring in the tone and data-selected, for the picked contact in a grid (drawn inside the edge on rows, so a list group never clips it). |
| `loading?` | `boolean` | `false` | Skeletons in the layout's shape (aria-busy); the content fades in when it turns false. |
| `showFields?` | `boolean` | `true` | Full layout: the rows for email, phone, website, location and CUI, then contact.fields. |
| `showTags?` | `boolean` | `true` | Full layout: the tag row. |
| `showActivity?` | `boolean` | `true` | Full layout: the footer with the owner and the last activity (a <time>, relative to now). |
| `copyable?` | `boolean` | `true` | Copy buttons on email, phone, CUI and facts with copyValue: on hover or focus with a mouse, always on touch. |
| `channels?` | `("call" \| "email" \| "whatsapp" \| "sms" \| "website")[]` | `"[\"call\", \"email\", \"whatsapp\"]"` | Which contact buttons show, in order. A channel without data (no phone, no email) is hidden; with none left the row disappears. |
| `onCall?` | `(contact: ContactData) => void` |  | Replaces the tel: links of the Call button and the phone field (a softphone). |
| `onEmail?` | `(contact: ContactData) => void` |  | Replaces the mailto: links of the Email button and the email field (an in-app composer). |
| `onMessage?` | `(contact: ContactData) => void` |  | Replaces the wa.me and sms: links. |
| `onChannelSelect?` | `(channel: ContactChannel, contact: ContactData, event: ContactChannelEvent) => void` |  | Runs first for every contact button, every channel picked in the row menu and the email / phone / website links of the fields. event.preventDefault() stops the link and the handler (tracking, a confirmation). |
| `actions?` | `MenuEntry[]` |  | The ⋯ menu (Edit, Add note, Merge, Delete with tone "danger"), a bottom sheet on phones. Rows put the channels on top. |
| `locale?` | `string` | `""` | Formats the last activity time ("45 min. ago"). Empty uses the UIProvider locale, then the browser's. |
| `now?` | `Date \| number` |  | The reference time for the relative activity time. Defaults to a live clock that ticks every minute. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | Renders the name, company and fact links through your router's Link. Defaults to the LinkProvider's, else <a>. |
| `labels?` | `Partial<ContactCardLabels>` | `"DEFAULT_CONTACT_CARD_LABELS"` | call, email, whatsapp, sms, website, phone, location, copy, copied, favorite, favoriteAdd, favoriteRemove, favoriteError, actions, cancel, owner, lastActivity, cui, noActivity, tags (the tag list's name), actionLabel(action, name) ("Call Emma Carter"), copyField(field) ("Copy Email"). |
| `classNames?` | `Partial<Record<"media" \| "heading" \| "name" \| "title" \| "company" \| "status" \| "favorite" \| "menu" \| "actions" \| "action" \| "fields" \| "field" \| "tags" \| "footer" \| "owner" \| "activity", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the <article>, merged last. |

## Parts

### ContactActions (component)

The contact buttons alone, for other layouts: contact, channels, layout ("tiles" labelled buttons in equal columns, "icons" round buttons with tooltips), variant (soft), tone, radius, onCall / onEmail / onMessage / onChannelSelect, labels. Props: ContactActionsProps.

### whatsappHref (function)

https://wa.me/40745123456 from "0745 123 456"; international numbers are kept as given, text becomes ?text=. Signature: (phone: string, text?: string) => string.

### DEFAULT_CONTACT_CARD_LABELS (constant)

The English strings. Spread it and override a few keys, or pass a full translated set through labels. Type: ContactCardLabels.

### telHref (function)

tel:0745123456 from "0745 123 456", tel:+40745123456 from "+40 745 123 456": spaces and punctuation dropped, a leading + kept. Signature: (phone: string) => string.

## Examples

- **Company**: A company card: logo initials in a rounded square, a tax ID (EIN) with copy, website and facts like the balance.
- **Compact grid**: The client contacts in a 2-column phone / 4-column desktop grid, with one selected.
- **Rows in a list**: One 56px line per contact with favorites; buttons appear on hover and stay visible on touch.
- **Custom channels**: Email and Call only, with onCall opening an in-app softphone instead of a tel: link.
- **Loading**: Skeletons in the shape of each layout; the content fades in when it arrives.
- **Tone and color**: Brand tone and a custom color paint the selected ring and the star.
- **British locale, scheduled call**: An en-GB locale; a future activity reads as “in 2 hr”.

## See also

- [Entity Header](https://wingo-ui.com/components/entity-header) ([Markdown](https://wingo-ui.com/components/entity-header.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Hover Card](https://wingo-ui.com/components/hover-card) ([Markdown](https://wingo-ui.com/components/hover-card.md))
- [Favorite button](https://wingo-ui.com/components/favorite-button) ([Markdown](https://wingo-ui.com/components/favorite-button.md))
- [List](https://wingo-ui.com/components/list) ([Markdown](https://wingo-ui.com/components/list.md))
- [Card](https://wingo-ui.com/components/card) ([Markdown](https://wingo-ui.com/components/card.md))

---

Canonical: https://wingo-ui.com/components/contact-card
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
