CRM Contact Page React block for Tailwind CSS v4
v1.0.1Updated in 1.9.0
A React CRM contact page: a company record with facts and actions, tabs for overview, activity, deals, files and notes, and a details panel.
The header shows the company with its status, a star and a follow toggle, key facts (copy the tax ID, open the last invoice) and actions: Email and Call open the mail app and the phone, New deal opens a validated dialog (title, value, stage, close date) and the new deal lands in the deals tab, the rest (New invoice, Add contact, Export record, Archive) fold into ⋯ when they do not fit and always on phones. Double click the name, press F2 or tap the pencil to rename it. The pager walks to the previous and next client (keys are off by default; the demo binds Alt + arrows). Once the header scrolls away a glass bar keeps the name and the primary action. The tabs (arrows, Home and End) switch Overview, Activity, Deals, Files and Notes; the panel slides in and the counts follow the data. Overview: the company details with copy buttons, the contacts, the open deals (View all jumps to the tab) and the last four events. Activity: write a comment (@ mentions a colleague, ⌘ Enter sends) and it lands at the top of today; the chips filter emails, calls, meetings and invoices and announce the count. Deals: a sortable table (cards on phones) with the open total. Files: drop or pick files, they upload with progress and join the list; preview, download or delete (asked first). Notes: a rich text editor with mentions, ⌘ Enter or Save note saves; pin, edit or delete a note from ⋯ (delete has an undo toast). The side panel shows the primary contact (call, email, WhatsApp), billing, tags (remove with ×, add from the field) and the team; on phones it is the Details tab. Archiving shows an alert with Restore and makes the record read-only. Failed saves roll back with a toast. Sections rise in once on mount; reduced motion keeps everything still.
CRM Contact Page is a Pro React block in the React blocks category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add crm-contact-page, together with Alert, Avatar, Badge, Button, Comment Thread and 32 more and the npm packages motion, lucide-react, react-hook-form and zod.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add crm-contact-pageCRM Contact Page is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import CrmContactPage from @/components/blocks/crm-contact-page.
// app/(admin)/clients/[id]/page.tsx (the shell lives in app/(admin)/layout.tsx: <AdminShell>{children}</AdminShell>)
"use client"
import { CrmContactPage, type CrmNote } from "@/components/blocks/crm-contact-page"
export default function ClientPage() {
return (
<CrmContactPage
previousShortcut="alt+arrowleft"
nextShortcut="alt+arrowright"
onNoteSave={async (note: CrmNote) => {
await fetch(`/api/clients/c01/notes/${note.id}`, { method: "PUT", body: JSON.stringify(note) })
}}
onTagsChange={async (tags) => {
await fetch("/api/clients/c01", { method: "PATCH", body: JSON.stringify({ tags }) })
}}
/>
)
}Props
companyCrmCompanyThe record ({ id, name, cui, regCom, status, industry, city, address, website, email, phone, iban, bank, owner, tags, createdAt, balance, revenueYtd, lastInvoice, lastPayment, overdueInvoices, paymentTermDays, followers, archived }); defaults to CRM_CONTACT_SAMPLE.company (Tech Solutions Inc.).
contactsContactData[]The people at the company; defaults to CRM_CONTACT_SAMPLE.contacts (Daniel Brooks, Laura Price, Sam Porter).
primaryContactIddefault"daniel-brooks"stringThe contact in the side panel card, listed first with the Primary badge; defaults to the first contact.
dealsCrmContactDeal[]The client's deals ({ id, title, stageId, amount, currency, probability, owner, expectedCloseDate, href }); defaults to CRM_CONTACT_SAMPLE.deals (3).
stagesCrmDealStage[]The pipeline stages the deals point to ({ id, name, probability, kind }); defaults to CRM_CONTACT_STAGES (B2B sales).
activityCrmActivity[]Timeline items with a kind (email, call, meeting, invoice, payment, stage, comment, note, file) for the icon and the filters; defaults to CRM_CONTACT_SAMPLE.activity (14).
filesFileItem[]The record's files; defaults to CRM_CONTACT_SAMPLE.files (6).
notesCrmNote[]Notes ({ id, author, createdAt, content (HTML), pinned, editedAt }); defaults to CRM_CONTACT_SAMPLE.notes (3).
peopleCommentAuthor[]Who can be @mentioned in comments and notes; defaults to CRM_CONTACT_TEAM.
currentUserCommentAuthorThe signed-in user: the composer avatar, the author of new notes and comments; defaults to Ana Pop.
tab"overview" | "activity" | "deals" | "files" | "notes"The open tab, controlled; pair it with onTabChange.
defaultTabdefault"overview""overview" | "activity" | "deals" | "files" | "notes"The tab open first while uncontrolled.
onTabChange(tab: CrmContactTab) => voidCalled when a tab is picked (also by View all and Add a note).
pagerdefault"3 of 128"EntityPager | nullPrevious / next client ({ index, total, onPrevious, onNext, previousHref, nextHref }); null hides it.
previousShortcutdefault""stringKey for the previous client, e.g. alt+arrowleft; empty binds nothing (the demo binds Alt + arrows).
nextShortcutdefault""stringKey for the next client, e.g. alt+arrowright; empty binds nothing.
noteShortcutdefault"mod+enter"stringSaves the note from inside the editor (shown as ⌘ Enter or Ctrl + Enter); empty binds nothing.
showAsidedefaulttruebooleanThe side panel with the primary contact, billing, tags and team (the Detalii tab on phones).
factsLayoutdefault"strip""strip" | "grid" | "tiles"The header facts: one scrolling strip, a grid, or gray tiles (grid and tiles fold after 4 on phones).
headerStickydefault"condensed""none" | "condensed"condensed pins a glass bar with the name and the primary action once the header scrolls away.
defaultUploadsUploadFile[]Uploads already running when the page opens; entries without a picked file finish on a timer (the files example).
autoFocusNotedefaultfalsebooleanThe note editor takes focus when the notes tab mounts.
defaultFavoritedefaulttruebooleanThe header star starts on.
defaultFollowingdefaulttruebooleanThe follow toggle starts on (you appear in the team list).
listHrefdefault"/clients"stringWhere Clients in the breadcrumb goes.
invoiceHrefdefault"/invoices/new?client=<id>"stringWhere New invoice goes (header menu and billing).
contactHrefdefault"/contacts/new?client=<id>"stringWhere Add contact goes.
onTitleChange(title: string) => (void | Promise<unknown>)Renaming from the header; a rejection restores the name and shows the error. Simulated by default.
onAction(action: CrmContactAction, company: CrmCompany) => (void | Promise<unknown>)Every header action. Archive and restore are optimistic (a rejection rolls back with a toast); export shows a loading toast.
onNoteSave(note: CrmNote) => (void | Promise<unknown>)A new note (new id) or a changed one (edit, pin); a rejection keeps the draft or rolls back with a toast.
onNoteDelete(note: CrmNote) => (void | Promise<unknown>)Deleting a note: removed at once, with an undo toast; a rejection brings it back.
onComment(body: string, context: { mentions: string[]; }) => (void | Promise<unknown>)A comment from the activity composer; the composer keeps the text until it resolves.
onUpload(file: File, context: { signal, onProgress }) => Promise<unknown>Uploads a picked file; done files join the list. Default: a pretend upload of about 1.6 s.
onFileDelete(file: FileItem) => (void | Promise<unknown>)Deleting a file after the confirmation; the row fades while it runs.
onFileOpen(file: FileItem) => voidClicking a file; default opens its preview url in a new tab.
onDealCreate(values: CrmNewDealValues) => (void | Promise<unknown>)The new deal dialog's submit; a rejection shows its message in the dialog.
onTagsChange(tags: string[]) => (void | Promise<unknown>)Tags added or removed in the side panel; optimistic, rolled back on rejection.
onFavoriteChange(favorite: boolean) => (void | Promise<unknown>)The header star.
onFollowingChange(following: boolean) => (void | Promise<unknown>)The follow toggle.
labelsdefault"DEFAULT_CRM_CONTACT_PAGE_LABELS"Partial<CrmContactPageLabels>Every visible string (English by default); CRM_CONTACT_PAGE_LABELS_RO is the ready Romanian set. Nested keys translate the composed items.
localedefault"en-US"stringIntl locale for money, numbers and dates.
currencydefault"USD"stringThe currency of the balance, revenue and deals.
nowdefault"useNow()"Date | numberThe reference time for relative times and the day headings; the demo pins Sep 24, 2026 16:40.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The accent of the primary actions, the tab indicator, the chips, the editor and the upload progress.
radiusdefault"md""sm" | "md" | "lg"The panels (12, 16 or 24px), the contact card, the dropzone, the editor and the dialog.
loadingdefaultfalsebooleanThe header, the sections and the side panel as skeletons.
animateIndefaulttruebooleanThe header, the panel and the side sections rise in once on mount; off in thumbnails and under reduced motion.
linkComponentdefault"<a>"(props: LinkComponentProps) => ReactNodeThe router link for the breadcrumb, the pager, the deal and invoice links; defaults to the LinkProvider's.
classNamestringMerged onto the root.
classNamesPartial<Record<CrmContactPageSlot, string>>Classes for the parts: header, tabs, panel, section, aside, archived, dealDialog.
Parts
DEFAULT_CRM_CONTACT_PAGE_LABELS
constantEvery visible string of the page in English, including the nested labels of the composed items. Spread it and override a few to translate. Type: CrmContactPageLabels.
CRM_CONTACT_PAGE_LABELS_EN
constantThe same English set under an explicit name, next to the Romanian one.
CRM_CONTACT_PAGE_LABELS_RO
constantThe ready Romanian set of every string, for the localization demo or a Romanian app.
OverviewPanel
componentThe Overview tab on its own: company details with copy buttons, the contacts, the open deals and the last four events. Takes ctx (CrmPanelContext), company, contacts, primaryContactId, deals, activity, onTabChange and onAddContact.
ActivityPanel
componentThe Activity tab: a comment box with @ mentions, the kind filter chips and the grouped timeline. Takes ctx, activity, people, currentUser, onComment, onAddNote and onAnnounce.
DealsPanel
componentThe Deals tab: a sortable table (cards on phones) with the open total and a New deal button. Takes ctx, deals and onNewDeal.
FilesPanel
componentThe Files tab: a drop zone with upload progress and the file list with preview, download and delete. Takes ctx, files, uploads, onUploadsChange, upload, onDelete, onPreview, onDownload and radius.
NotesPanel
componentThe Notes tab: a rich text editor with mentions and the saved notes with pin, edit and delete. Takes ctx, notes, people, currentUser, noteShortcut, autoFocus, radius, onCreate, onUpdate, onDelete and onAnnounce.
toTimelineItem
functionTurns a CRM activity into a Timeline item, filling in the icon of its kind. Signature: (item: CrmActivity) => TimelineItemData.
stageTone
functionThe badge tone of a deal stage: success when won, danger when lost, neutral otherwise. Signature: (stage: CrmDealStage | undefined) => Tone.
ACTIVITY_ICONS
constantThe lucide icon per activity kind (email, call, meeting, invoice, payment, stage, comment, note, file).
CRM_CONTACT_SAMPLE
constantSample data: Tech Solutions Inc. with its contacts, 3 deals, 14 activity items, 6 files and 3 notes. The page's default content.
CRM_CONTACT_ARCHIVED_SAMPLE
constantSample data for an archived record (Westside Books, a former client), shown read-only.
CRM_CONTACT_UPLOAD_SAMPLE
constantSample uploads in progress, for the files example.
CRM_CONTACT_TEAM
constantSample team members with roles, the people you can @mention.
CRM_CONTACT_CURRENT_USER
constantThe sample signed-in user who writes comments and notes.
CRM_CONTACT_STAGES
constantSample B2B pipeline stages with probabilities: Qualified, Needs analysis, Proposal sent, Negotiation, Won and Lost.
CRM_CONTACT_NOW
constantThe fixed date the sample data is relative to, so relative times read the same on every render.
CrmPanelContext
typeWhat the page hands every tab panel: labels, locale, currency, tone, the section surface class, now, loading, archived, stages and the money, day, relative and percent formatters.
CrmContactSample
type{ company, contacts, primaryContactId?, deals, activity, files, notes }, the shape of a record's data.
Examples
Activity
defaultTab="activity": the comment composer, the filter chips and 14 events grouped by day. Alt + arrows walk the pager.
Notes, editor focused
defaultTab="notes" with autoFocusNote: write, then press ⌘ Enter to save; pin, edit or delete from ⋯.
Files, one upload running
defaultUploads: Delivery-acceptance-report.pdf finishes uploading and joins the list.
Archived client
Westside Books: the Archived status, the Restore alert and read-only actions.
Loading
loading: the header, the sections, the contact card and the side panel as skeletons.
Brand tone, tiles
tone="brand", radius="lg", factsLayout="tiles", on the deals tab.
Romanian copy, RON
CRM_CONTACT_PAGE_LABELS_RO, locale="ro-RO", currency="RON": the ready Romanian set for the page and its parts.
Dependencies
Files
- components/blocks/crm-contact-page.tsx
- components/blocks/crm-contact-page-tabs.tsx
- components/blocks/crm-contact-page-data.ts
FAQ
Is the React CRM Contact Page block free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install CRM Contact Page?
Run npx wingo-ui@latest add crm-contact-page. It also adds the Wingo UI items it builds on (Alert, Avatar, Badge, Button, Comment Thread and 32 more) and the npm packages motion, lucide-react, react-hook-form and zod. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/crm-contact-page.
How does CRM Contact Page behave on phones?
The header shows the company with its status, a star and a follow toggle, key facts (copy the tax ID, open the last invoice) and actions: Email and Call open the mail app and the phone, New deal opens a validated dialog (title, value, stage, close date) and the new deal lands in the deals tab, the rest (New invoice, Add contact, Export record.
See also
More React blocks
Articles using this component
- React CRM Template: Pipeline, Forecast, Company Pages
Build it: screens and apps11 min read