Contact List Pattern
Use this pattern for any entity list page with search, filtering, and status badges.
Live Demo
Showing 5 of 5 contacts
| Name | Company | Status | Actions | |
|---|---|---|---|---|
| Sarah Chen CTO | Customer | |||
| Marcus Johnson Founder | Lead | |||
| Elena Rodriguez Partner | Active | |||
| David Kim VP Engineering | Customer | |||
| Aisha Patel CEO | Inactive |
Source
'use client';
import { Button } from '@stackmates/ui-interactive/atoms';
import Link from 'next/link';
import { useMemo, useState } from 'react';
import { CONTACTS, STATUS_COLORS, STATUS_LABELS, STATUS_OPTIONS,
searchContacts, type Contact, type ContactStatus } from '../_data';
export default function ContactListPage() {
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState<ContactStatus | 'all'>('all');
const filteredContacts = useMemo(() => {
let result: Contact[] = CONTACTS;
if (searchQuery.trim()) result = searchContacts(searchQuery);
if (statusFilter !== 'all') result = result.filter(c => c.status === statusFilter);
return result;
}, [searchQuery, statusFilter]);
return (
<div>
{/* Toolbar */}
<div className="mb-6 flex justify-between">
<input type="search" placeholder="Search..." value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} />
<select value={statusFilter}
onChange={e => setStatusFilter(e.target.value as ContactStatus | 'all')}>
<option value="all">All</option>
{STATUS_OPTIONS.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
</select>
<Link href="/crm/contacts/new"><Button>Add Contact</Button></Link>
</div>
{/* Table */}
<table>
<thead><tr>
<th>Name</th><th>Email</th><th>Status</th><th>Actions</th>
</tr></thead>
<tbody>
{filteredContacts.map(c => (
<tr key={c.id}>
<td>{c.firstName} {c.lastName}</td>
<td>{c.email}</td>
<td><span className={STATUS_COLORS[c.status]}>{STATUS_LABELS[c.status]}</span></td>
<td><Link href={`/crm/contacts/${c.id}`}><Button variant="ghost" size="sm">View</Button></Link></td>
</tr>
))}
</tbody>
</table>
</div>
);
}Copy these 2 files
crud/_data.tsTypes, mock data, status colors, search helperscrud/contact-list/page.tsxList page with table, search, and filterCustomization
- Replace Contact with your entity name in _data.ts
- Update field names (firstName, lastName, etc.) to match your entity
- Adjust STATUS_COLORS and STATUS_LABELS for your status enum
- Replace console.log with actual server actions for production
- Add pagination via DataTable if list exceeds 50 items
Anti-patterns
Wrong
Build search + filter from scratch in each list page
Right
Copy this pattern or use SearchFilterBar molecule
Wrong
Use raw <table> without search/sort for production
Right
Use DataTable from @stackmates/ui-interactive/organisms
Wrong
Hardcode status colors inline
Right
Define STATUS_COLORS map in data file