# Rapport — Contacts App & CRM for React, shadcn/ui & Tailwind (Address Book UI)

> A complete contacts manager for React and shadcn/ui: folders and labels, highlighted search, filters, list, grid and table views, details, activity and notes, validated forms with photo crop, bulk actions with Undo, CSV and vCard import/export. Light and dark, phone to desktop.

- Kind: Component (Tables)
- Price: comes with the Pro plan
- License: UpFork Standard License. Use it in unlimited personal and commercial projects. Don't resell or redistribute it on its own.
- Developer: UpFork
- Tags: table, list, form, dashboard, contacts, crm, address-book, people, shadcn, base-ui, nextjs, tailwind
- npm packages: @base-ui/react, @hookform/resolvers, class-variance-authority, libphonenumber-js, lucide-react, motion, react-easy-crop, react-hook-form, sonner, zod, zustand
- Published: 2026-10-16
- Updated: 2026-10-16
- Page: https://upfork.dev/products/shadcn-contacts-app-crm-react

## How to get it

Install it into a React + Tailwind project with the shadcn CLI:

```bash
npx shadcn@latest add @upfork/shadcn-contacts-app-crm-react
```

It installs with the member's personal CLI token (UPFORK_TOKEN), made on their UpFork account page. It comes with UpFork's Pro plan and the plans after it.

## About

A complete contacts and light-CRM screen (the dashboard's `/apps/contact`) built on shadcn/ui with **Base UI**
primitives, Tailwind CSS 4, `zustand`, `react-hook-form` + `zod`, `libphonenumber-js`, `react-easy-crop`, `motion`
and `sonner`. shadcn's neutral palette only (plus six label dots), light and dark, phone to wide desktop.

```bash
npm install
npm run dev                        # http://localhost:5501
npm run build && npm run start     # review build on http://localhost:5500
```

```tsx
import { Contacts } from "@/components/contacts/contacts"

<Contacts />                                        // the demo address book (33 people)
<Contacts contacts={mine} labels={myLabels} links={{ email: (c) => `/apps/mail?compose=${c.email}` }} />
```

Mount one sonner `<Toaster />` in your app (as `app/layout.tsx` does): every change shows a toast with Undo.

## What is in it

- **Sidebar:** New Contact, All Contacts / Favourites / Spam / Blocked and six labels with live counts (they follow
  the filters), an active bar, collapsible labels, New label (name + one of the six hues), a ⋮ per label (Rename,
  Change color, Delete label), Import and Export. A sheet behind "Open navigation" on narrow screens.
- **Toolbar:** search (`/`, debounced, clear button) across name, e-mail, phone digits, city, company and notes with
  the match highlighted; Sorted A-Z / Z-A plus Name, Recently added, City and Status; a Filter popover (status,
  labels with any / all, city, date added, has phone, is favourite) with a count badge; List / Grid / Table; select
  mode. Under it, a removable chip for every filter, Clear all and "12 of 25 contacts".
- **List:** sticky A–Z groups with counts, an A–Z rail (press or drag), rows with photo, name, favourite star, a
  "New" badge (added in the last 30 days), the full e-mail (or the matched note, company or phone), status (solid,
  hollow or dashed dot and the word), up to two label chips and "+N" with the rest in a tooltip, hover quick
  actions (Call, Email, Message, Favourite), a ⋮ menu and the same right-click menu. Phone and city columns when the
  list is wide; avatar, name, status and label dots on phones.
- **Grid:** cards with a neutral cover, the photo over it, status, e-mail, chips, Call / Email / Message, a star and ⋮.
  1 to 4 columns by width.
- **Table:** select, Name, Email, Phone, City, Labels, Status, Added on, Favourite; sortable headers, column
  visibility and density (View). It takes the whole width; details open in a sheet.
- **Menus by folder:** All / Favourites / labels: Edit, Favourite, Spam, Block, Delete. Spam: Edit, Not spam, Block,
  Delete. Blocked: Unblock. Spam and Blocked have a short note and Not spam / Unblock all.
- **Selecting:** select mode or the checkbox over any photo, Shift+click ranges, Ctrl/⌘+click, Space; "Select all"
  (indeterminate when partial), "Select all 25 matching", Delete; a floating bar with Labels (add / remove),
  Favourite, Spam / Not spam, Block / Unblock, Export and Delete (confirmed with the count).
- **Detail** (third pane on wide screens, a sheet on tablets, full screen with Back on phones): dotted cover, 96 px
  photo, name, favourite toggle, status, chips, e-mail; Call (`tel:`), Email, Message, Schedule (an .ics invite)
  and More (Edit, Favourite, Spam, Block, Delete, Copy email, Copy phone, Export vCard, Share); previous / next
  (`J` / `K`); Overview (phone "+1 (753) 456-7890" and e-mail with copy, city, added on with a relative time,
  company, job title, note, labels), Activity (a timeline) and Notes (yours editable, a teammate's read-only).
- **Forms:** Edit inline in the detail, New Contact in a dialog (a sheet on phones). Photo by click or drop with a
  circle crop and Remove; first and last name, e-mail (checked, duplicates flagged with "View existing contact"),
  phone with a country code and live formatting, city with suggestions, company, job title, status, label toggles
  (+ New label), favourite, note, added on (read-only). Errors under each field, Save disabled until valid,
  `Ctrl/⌘+Enter` saves, `Esc` cancels, and "Discard unsaved changes?" before anything is lost.
- **Import / export:** CSV (drop zone, first 5 rows, column mapping, "12 new, 2 duplicates skipped") and export of
  the view or the selection as CSV or vCard. All in the browser.
- **Shortcuts** (`?` lists them): `/` `N` `↑` `↓` `Enter` `J` `K` `E` `F` `X` `Delete` `Esc`, `g a` / `g f` / `g s` /
  `g b`, `1` / `2` / `3`. They pause while you type.
- **State in the URL:** `?folder=spam&label=lead&status=active&q=…&view=grid&id=<contact>`; view, sort, density and
  columns are remembered in `localStorage`.

## Props

| Prop | |
| --- | --- |
| `contacts`, `labels` | your data (`Contact[]`, `Label[]` from `components/contacts/types.ts`); the demo book otherwise |
| `links` | `{ call, email, message, schedule }`, each `(contact) => href`: point Email, Message and Schedule into your Mail, Chat and Calendar apps. Defaults: `tel:`, `mailto:`, `sms:` and an .ics invite |
| `onContactsChange` | `(contacts, labels) => void` after every change, to save them |
| `defaultContactId`, `defaultView` | what opens first when the URL doesn't say |
| `syncUrl` | `false` keeps the URL alone (live previews, several instances) |

## Where things live

| Path | |
| --- | --- |
| `components/contacts/contacts.tsx` | `Contacts`: panes by its own width, URL state, sheets and dialogs |
| `components/contacts/logic.ts` | pure helpers: filters, search, sort, groups, counts, phone and date formats, CSV, vCard, .ics |
| `components/contacts/store.ts` | the zustand store and the pure edits of the book (`ops`) |
| `components/contacts/commands.ts` | what buttons do: toasts with Undo, confirmations, the unsaved-changes guard |
| `components/contacts/data.ts` | the demo book: labels, statuses, notes, activity, the sample CSV |
| `lib/people.ts` | the people (name, e-mail, E.164 phone, city, company, photo), shared by the dashboard's apps |
| `components/ui/` | the shadcn/ui primitives (Base UI flavour) |
| `public/images/avatars/` | the 35 profile photos (`npm run avatars`, credits in `CREDITS.md`) |

Photos: free Unsplash photos chosen to fit each name; people who also appear in the Mail and Chat apps (John Doe,
Emma Wilson, Rachel Green, Alex Thompson, Sarah Johnson) have the same photo there. Names, companies, numbers
(555-01xx and the other ranges kept for fiction) and notes are invented.

## Checks

| Command | |
| --- | --- |
| `npm run lint`, `npm run typecheck`, `npm run build` | ESLint, TypeScript, the production build |
| `npm test` | 15 unit tests of `logic.ts` and the demo data (Vitest) |
| `node scripts/e2e.mjs` | 82 clicks through every feature in Chrome (against `RAPPORT_URL`, default :5500) |
| `node scripts/a11y.mjs` | axe on 11 screens, light and dark |
| `npm run shots` | screenshots: 12 views × light / dark × 1440, 1280, 1024, 768, 390 px into `screenshots/` |
| `npm run registry`, `npm run zip` | the listing copy in `registry/` and `rapport.zip` for UpFork |
