Last updated: Aug 4, 2025, 11:26 AM UTC

Contact Management Wireframes with shadcn/ui

Status: Contact Interface Specifications
Framework: shadcn/ui + Radix UI + Tailwind CSS
Verified: Based on user research and competitor pain points
Reference: UI Architecture Guide


Executive Summary

Contact management should be invisible until you need it. These wireframes present a radically simplified approach to managing email contacts, addressing the core finding that users waste hours navigating complex CRM-like interfaces when they just need basic list management.

Design Philosophy

Based on Phase 1 findings of "CRM feature creep," our approach:

  • Table view by default (not complex CRM cards)
  • Inline editing (no popup modals)
  • Smart bulk actions (common tasks prioritized)
  • One-click import/export (no 10-step wizards)
  • Visual tags and lists (color-coded recognition)

The Simplicity Advantage

graph LR A[Competitor Approach] --> B[CRM complexity] B --> C[Custom fields overload] C --> D[Steep learning curve] E[Our Approach] --> F[Email-first design] F --> G[Essential fields only] G --> H[5-minute mastery] style D fill:#ffebee style H fill:#e8f5e9

Contact List View Wireframe

Contact List View

Evidence-Based Design Decisions

Feature Traditional CRM Our Solution User Benefit
Default View Complex cards Clean table Scan 50 contacts at once
Contact Fields 50+ custom fields 8 essential fields No overwhelm
Bulk Actions Buried in menus Persistent toolbar One-click operations
Search Advanced filters Simple search + filters Find anyone in seconds
Import/Export Multi-step wizard One-click process 2-minute migration

Page Layout Structure

Header Section (140px)

Components:

  • Page title with total count
  • Quick stats (subscribed/unsubscribed)
  • Global navigation

Key Metrics Display:

Contacts
2,847 total contacts โ€ข 2,103 subscribed โ€ข 744 unsubscribed

Action Bar with shadcn/ui (40px)

// Action bar using shadcn/ui components
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { Badge } from "@/components/ui/badge"
import { Search, Filter, Download, Upload, Plus, X } from "lucide-react"

export function ContactActionBar({ filters, onSearch, onFilter, onImport, onExport }) {
  return (
    <div className="flex items-center justify-between p-4 border-b">
      {/* Left Side - Discovery */}
      <div className="flex items-center gap-3">
        {/* Search Box */}
        <div className="relative w-[300px]">
          <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
          <Input 
            placeholder="Search contacts..." 
            className="pl-8"
            onChange={(e) => onSearch(e.target.value)}
          />
          {filters.search && (
            <Button
              variant="ghost"
              size="icon"
              className="absolute right-1 top-1 h-7 w-7"
              onClick={() => onSearch('')}
            >
              <X className="h-4 w-4" />
            </Button>
          )}
        </div>
        
        {/* Quick Filters */}
        <div className="flex items-center gap-2">
          <Button variant="outline" size="sm">
            <Filter className="mr-2 h-4 w-4" />
            Filters
            {filters.count > 0 && (
              <Badge variant="secondary" className="ml-2">
                {filters.count}
              </Badge>
            )}
          </Button>
          
          <Select>
            <SelectTrigger className="w-[150px]">
              <SelectValue placeholder="All Lists" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Lists</SelectItem>
              <SelectItem value="newsletter">Newsletter</SelectItem>
              <SelectItem value="customers">Customers</SelectItem>
              <SelectItem value="leads">Leads</SelectItem>
            </SelectContent>
          </Select>
          
          <Select>
            <SelectTrigger className="w-[150px]">
              <SelectValue placeholder="All Tags" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Tags</SelectItem>
              <SelectItem value="vip">VIP</SelectItem>
              <SelectItem value="beta">Beta</SelectItem>
              <SelectItem value="trial">Trial</SelectItem>
            </SelectContent>
          </Select>
        </div>
      </div>
      
      {/* Right Side - Actions */}
      <div className="flex items-center gap-2">
        <Button variant="outline" onClick={onExport}>
          <Download className="mr-2 h-4 w-4" />
          Export
        </Button>
        <Button variant="outline" onClick={onImport}>
          <Upload className="mr-2 h-4 w-4" />
          Import
        </Button>
        <Button>
          <Plus className="mr-2 h-4 w-4" />
          Add Contact
        </Button>
      </div>
    </div>
  )
}

Bulk Actions Bar with shadcn/ui (50px)

// Bulk actions bar using shadcn/ui components
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Tag, Mail, FolderOpen, Trash2 } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"

export function BulkActionsBar({ selectedCount, totalCount, onSelectAll, onAction }) {
  return (
    <AnimatePresence>
      {selectedCount > 0 && (
        <motion.div
          initial={{ height: 0, opacity: 0 }}
          animate={{ height: 50, opacity: 1 }}
          exit={{ height: 0, opacity: 0 }}
          transition={{ duration: 0.2 }}
        >
          <Alert className="rounded-none border-x-0 bg-blue-50 dark:bg-blue-950">
            <AlertDescription className="flex items-center justify-between">
              <div className="flex items-center gap-4">
                <Checkbox
                  checked={selectedCount === totalCount}
                  onCheckedChange={onSelectAll}
                />
                <span className="font-medium">
                  {selectedCount} of {totalCount} contacts selected
                </span>
                
                {/* Bulk Actions */}
                <div className="flex items-center gap-2 ml-4">
                  <Button 
                    variant="outline" 
                    size="sm"
                    onClick={() => onAction('tag')}
                  >
                    <Tag className="mr-2 h-4 w-4" />
                    Add Tag
                  </Button>
                  <Button 
                    variant="outline" 
                    size="sm"
                    onClick={() => onAction('email')}
                  >
                    <Mail className="mr-2 h-4 w-4" />
                    Send Email
                  </Button>
                  <Button 
                    variant="outline" 
                    size="sm"
                    onClick={() => onAction('move')}
                  >
                    <FolderOpen className="mr-2 h-4 w-4" />
                    Move to List
                  </Button>
                  
                  {/* Delete with confirmation */}
                  <AlertDialog>
                    <AlertDialogTrigger asChild>
                      <Button 
                        variant="outline" 
                        size="sm"
                        className="text-destructive"
                      >
                        <Trash2 className="mr-2 h-4 w-4" />
                        Delete
                      </Button>
                    </AlertDialogTrigger>
                    <AlertDialogContent>
                      <AlertDialogHeader>
                        <AlertDialogTitle>Are you sure?</AlertDialogTitle>
                        <AlertDialogDescription>
                          This will permanently delete {selectedCount} contacts.
                          This action cannot be undone.
                        </AlertDialogDescription>
                      </AlertDialogHeader>
                      <AlertDialogFooter>
                        <AlertDialogCancel>Cancel</AlertDialogCancel>
                        <AlertDialogAction
                          onClick={() => onAction('delete')}
                          className="bg-destructive text-destructive-foreground"
                        >
                          Delete Contacts
                        </AlertDialogAction>
                      </AlertDialogFooter>
                    </AlertDialogContent>
                  </AlertDialog>
                </div>
              </div>
              
              <Button 
                variant="ghost" 
                size="sm"
                onClick={() => onAction('clear')}
              >
                Clear Selection
              </Button>
            </AlertDescription>
          </Alert>
        </motion.div>
      )}
    </AnimatePresence>
  )
}

Contact Table with shadcn/ui (560px)

Column Structure Implementation:

// Contact table using shadcn/ui components
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
import { Checkbox } from "@/components/ui/checkbox"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ArrowUpDown, MoreHorizontal, Mail, Edit2, Trash2 } from "lucide-react"

export function ContactTable({ contacts, onSort, selectedIds, onSelectionChange }) {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead className="w-[50px]">
            <Checkbox 
              checked={selectedIds.length === contacts.length}
              onCheckedChange={onSelectionChange}
            />
          </TableHead>
          <TableHead className="w-[300px]">
            <Button variant="ghost" onClick={() => onSort('email')}>
              Email
              <ArrowUpDown className="ml-2 h-4 w-4" />
            </Button>
          </TableHead>
          <TableHead className="w-[200px]">
            <Button variant="ghost" onClick={() => onSort('name')}>
              Name
              <ArrowUpDown className="ml-2 h-4 w-4" />
            </Button>
          </TableHead>
          <TableHead className="w-[200px]">Lists</TableHead>
          <TableHead className="w-[200px]">Tags</TableHead>
          <TableHead className="w-[150px]">
            <Button variant="ghost" onClick={() => onSort('created_at')}>
              Created
              <ArrowUpDown className="ml-2 h-4 w-4" />
            </Button>
          </TableHead>
          <TableHead className="w-[150px]">Status</TableHead>
          <TableHead className="w-[130px]">Actions</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {contacts.map((contact) => (
          <TableRow 
            key={contact.id}
            className="hover:bg-muted/50"
          >
            <TableCell>
              <Checkbox 
                checked={selectedIds.includes(contact.id)}
                onCheckedChange={() => onSelectionChange(contact.id)}
              />
            </TableCell>
            <TableCell className="font-medium">
              <div className="flex items-center gap-2">
                <Avatar className="h-8 w-8">
                  <AvatarImage src={contact.avatar} />
                  <AvatarFallback>{contact.initials}</AvatarFallback>
                </Avatar>
                <span>{contact.email}</span>
              </div>
            </TableCell>
            <TableCell>{contact.name}</TableCell>
            <TableCell>
              <div className="flex gap-1 flex-wrap">
                {contact.lists.map((list) => (
                  <Badge key={list} variant="secondary">
                    {list}
                  </Badge>
                ))}
              </div>
            </TableCell>
            <TableCell>
              <div className="flex gap-1 flex-wrap">
                {contact.tags.map((tag) => (
                  <Badge key={tag} variant="outline">
                    {tag}
                  </Badge>
                ))}
              </div>
            </TableCell>
            <TableCell>{contact.created}</TableCell>
            <TableCell>
              <Badge 
                variant={contact.status === 'subscribed' ? 'default' : 'destructive'}
              >
                {contact.status}
              </Badge>
            </TableCell>
            <TableCell>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button variant="ghost" className="h-8 w-8 p-0">
                    <span className="sr-only">Open menu</span>
                    <MoreHorizontal className="h-4 w-4" />
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end">
                  <DropdownMenuItem>
                    <Mail className="mr-2 h-4 w-4" />
                    Send Email
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <Edit2 className="mr-2 h-4 w-4" />
                    Edit Contact
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem className="text-destructive">
                    <Trash2 className="mr-2 h-4 w-4" />
                    Delete
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

Row Design:

  • 60px height (comfortable clicking)
  • Alternating white/gray backgrounds
  • Hover state: light blue
  • Selected state: blue outline

Mobile Contact Management

Responsive Adaptations

For Sarah Chen's mobile usage:

List View (375px)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Contacts (2,847)โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [๐Ÿ” Search...] โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ SC Sarah... โ”‚ โ”‚
โ”‚ โ”‚ Newsletter  โ”‚ โ”‚
โ”‚ โ”‚ VIP โ€ข Sub   โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ MR Marcus.. โ”‚ โ”‚
โ”‚ โ”‚ Newsletter  โ”‚ โ”‚
โ”‚ โ”‚ Beta โ€ข Sub  โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [+] Add Contact โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Mobile Optimizations

  • Card view replaces table
  • Essential info only (email, lists, status)
  • Swipe actions (edit, delete)
  • Sticky search at top
  • Floating add button

Tag and List Management

Visual Tag System

Based on Phase 2 research showing visual recognition beats text:

Color-Coded Tags:
๐ŸŸข Green: Positive (Customer, VIP, Active)
๐Ÿ”ต Blue: Informational (Newsletter, Updates)
๐ŸŸก Orange: Warning (At Risk, Inactive)
๐ŸŸฃ Purple: Special (Beta, Power User)
๐Ÿ”ด Red: Negative (Unsubscribed, Bounced)

List Organization

Default Lists (System):
โ”œโ”€โ”€ All Contacts
โ”œโ”€โ”€ Subscribed
โ”œโ”€โ”€ Unsubscribed
โ””โ”€โ”€ Bounced

Custom Lists (User):
โ”œโ”€โ”€ Newsletter
โ”œโ”€โ”€ Customers
โ”œโ”€โ”€ Leads
โ””โ”€โ”€ [+ Create List]

Search and Filter Patterns

Quick Search Behavior

User types: "sarah"
Searches in: email, first name, last name
Results: Instant filtering of table
Clear: X button appears when typing

Filter Combinations

Common filters (from user research):

  1. Status: Subscribed/Unsubscribed
  2. List Membership: In specific lists
  3. Tags: Has/doesn't have tags
  4. Date Range: Joined within X days
  5. Engagement: Opened/clicked recently

Smart Filter Suggestions

Recent Filters:
- "Subscribed + Newsletter + Joined last 30 days"
- "VIP tag + Customer list"
- "Unsubscribed in last 7 days"
[Save as Smart List]

Import/Export Flows

One-Click Export

Click Export โ†’
โ”œโ”€โ”€ Preparing CSV...
โ”œโ”€โ”€ Including: Email, Name, Lists, Tags, Status, Created
โ”œโ”€โ”€ 2,847 contacts exported
โ””โ”€โ”€ Download: contacts-2025-07-28.csv

Drag-Drop Import

Drag CSV file โ†’
โ”œโ”€โ”€ Reading file...
โ”œโ”€โ”€ Found 500 contacts
โ”œโ”€โ”€ Mapping fields (auto-detected)
โ”œโ”€โ”€ Checking for duplicates
โ”œโ”€โ”€ 450 new, 50 updates
โ””โ”€โ”€ [Import] [Cancel]

Import Field Mapping

Your CSV          โ†’ NudgeCampaign Fields
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Email Address     โ†’ Email (required)
First Name        โ†’ First Name
Last Name         โ†’ Last Name
Company           โ†’ [Skip]
Newsletter        โ†’ List: Newsletter
Customer Type     โ†’ Tag: [Create New]

Individual Contact View

Contact Detail Sidebar (Slide-in)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ† Sarah Chen           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ (SC) sarah@example.com โ”‚
โ”‚ Subscribed โ€ข Customer  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CONTACT INFO           โ”‚
โ”‚ Name: Sarah Chen       โ”‚
โ”‚ Email: sarah@example   โ”‚
โ”‚ Phone: [Add]          โ”‚
โ”‚ Company: [Add]        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ LISTS & TAGS          โ”‚
โ”‚ Lists: Newsletter โœ“    โ”‚
โ”‚        Customers โœ“     โ”‚
โ”‚ Tags: VIP, Active     โ”‚
โ”‚       [+ Add Tag]     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ACTIVITY              โ”‚
โ”‚ Joined: May 15, 2025  โ”‚
โ”‚ Emails Sent: 12       โ”‚
โ”‚ Opens: 8 (66%)        โ”‚
โ”‚ Clicks: 3 (25%)       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [Edit] [Send Email]   โ”‚
โ”‚ [Unsubscribe] [Delete]โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Performance Optimizations

Based on Phase 8 Architecture

Progressive Loading

  1. Initial Load (0-500ms)

    • First 50 contacts
    • Basic table structure
    • Search/filter UI
  2. Background Load (500-2000ms)

    • Remaining contacts
    • Tag/list metadata
    • Activity summaries

Virtual Scrolling

// Only render visible rows
const visibleRows = 20;
const rowHeight = 60;
const totalHeight = contactCount * rowHeight;
// Render only contacts in viewport

Bulk Operation Optimization

  • Process in batches of 100
  • Show progress indicator
  • Allow cancel mid-operation
  • Queue operations for reliability

Validation Against Success Metrics

Phase 1-2 Requirements

  • Simple interface: Table view dominates
  • Fast operations: Bulk actions prominent
  • Easy migration: One-click import/export
  • Mobile ready: Responsive card view

Phase 3-4 Technical

  • Fast loading: Virtual scroll ready
  • Scalable: Handles 100k+ contacts
  • Real-time: Live search results
  • Accessible: Keyboard navigation

Phase 5-7 Business

  • Growth-friendly: Easy list building
  • Segmentation: Tags and lists visible
  • Integration ready: API-friendly structure
  • GDPR compliant: Export/delete tools

๐Ÿ”ฎ Progressive Enhancement

MVP Features

  • Basic table view
  • Search by email/name
  • Add/edit/delete contacts
  • Simple import/export
  • Lists and tags

Phase 2 Additions

  • Advanced filters
  • Custom fields (limited)
  • Bulk email sending
  • Activity timeline
  • Duplicate management

Phase 3 Enhancements

  • Smart lists (auto-updating)
  • Engagement scoring
  • Predictive segmentation
  • API access
  • Zapier integration

Implementation Notes

Component Structure

ContactManagement/
โ”œโ”€โ”€ ContactTable/
โ”‚   โ”œโ”€โ”€ TableHeader
โ”‚   โ”œโ”€โ”€ TableRow
โ”‚   โ”œโ”€โ”€ BulkActions
โ”‚   โ””โ”€โ”€ Pagination
โ”œโ”€โ”€ ContactFilters/
โ”‚   โ”œโ”€โ”€ SearchBox
โ”‚   โ”œโ”€โ”€ FilterDropdowns
โ”‚   โ””โ”€โ”€ SavedFilters
โ”œโ”€โ”€ ContactActions/
โ”‚   โ”œโ”€โ”€ ImportModal
โ”‚   โ”œโ”€โ”€ ExportButton
โ”‚   โ””โ”€โ”€ AddContactForm
โ””โ”€โ”€ ContactDetail/
    โ”œโ”€โ”€ ContactInfo
    โ”œโ”€โ”€ ActivityFeed
    โ””โ”€โ”€ QuickActions

State Management

  • Contact list array
  • Selected contacts Set
  • Active filters object
  • Sort configuration
  • Pagination state

Performance Checklist

  • Implement virtual scrolling for large lists
  • Debounce search input (300ms)
  • Lazy load contact details
  • Cache filter results
  • Optimize bulk operations

Contact management wireframes designed to make list management invisible until needed, then powerful when required.