Design System

Contact List Pattern

Use this pattern for any entity list page with search, filtering, and status badges.

Live Demo

Showing 5 of 5 contacts

NameStatusActions
Sarah Chen

CTO

Customer
Marcus Johnson

Founder

Lead
Elena Rodriguez

Partner

Active
David Kim

VP Engineering

Customer
Aisha Patel

CEO

Inactive

Source

tsx
'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 helpers
crud/contact-list/page.tsxList page with table, search, and filter

Customization

  1. Replace Contact with your entity name in _data.ts
  2. Update field names (firstName, lastName, etc.) to match your entity
  3. Adjust STATUS_COLORS and STATUS_LABELS for your status enum
  4. Replace console.log with actual server actions for production
  5. 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