8.5 KiB
8.5 KiB
Compact Info Card Design
Problem Statement
The original design used large square photo grids that consumed excessive screen space, making it difficult to scan through multiple dogs quickly. Photos were displayed at 1:1 aspect ratio taking up 50-100% of card width.
Solution: Horizontal Info Cards
Transformed to a compact horizontal card layout with small avatar photos and prominent metadata, optimized for information scanning and list navigation.
Design Specifications
Layout Structure
┌─────────────────────────────────────────────────────────────────┐
│ [Avatar] Name ♂ Breed • Age • Color → │
│ 80x80 Golden Retriever #REG-12345 │
└─────────────────────────────────────────────────────────────────┘
Card Components
1. Avatar Photo (80x80px)
- Size: Fixed 80px × 80px
- Shape: Rounded corners (var(--radius))
- Border: 2px solid var(--border)
- Background: var(--bg-primary) when no photo
- Fallback: Dog icon at 32px, muted color
- Object Fit: cover (crops to fill square)
2. Info Section (Flex: 1)
- Name: 1.125rem, bold, truncate with ellipsis
- Sex Icon: Colored ♂/♀ (blue for male, pink for female)
- Metadata Row:
- Breed name
- Age (calculated, with calendar icon)
- Color (if available)
- Separated by bullets (•)
- Registration Badge:
- Monospace font
- Hash icon prefix
- Dark background pill
- 1px border
3. Arrow Indicator
- Icon: ArrowRight at 20px
- Color: var(--text-muted)
- Opacity: 0.5 default, increases on hover
- Purpose: Visual affordance for clickability
Space Comparison
Before (Square Grid)
[===============]
[ Photo ]
[ 300x300 ]
[===============]
Name
Breed • Sex
Height: ~380px per card Width: 280-300px Photos per viewport: 2-3 (desktop)
After (Horizontal Card)
[Avatar] Name, Breed, Age, Badge →
80x80
Height: ~100px per card Width: Full container width Cards per viewport: 6-8 (desktop)
Metrics
| Metric | Before | After | Improvement |
|---|---|---|---|
| Card height | 380px | 100px | -74% |
| Photo area | 90,000px² | 6,400px² | -93% |
| Scannable info | 2-3 cards | 6-8 cards | +200% |
| Scroll distance | 760px | 200px | -74% |
Implementation Details
React Component Structure
<Link to={`/dogs/${dog.id}`} className="card">
{/* Avatar */}
<div className="avatar-80">
{photo ? <img src={photo} /> : <Dog icon />}
</div>
{/* Info */}
<div className="info-section">
<h3>{name} <span>{sex icon}</span></h3>
<div className="metadata">
{breed} • {age} • {color}
</div>
<div className="badge">
<Hash /> {registration}
</div>
</div>
{/* Arrow */}
<ArrowRight />
</Link>
CSS Styling
.card {
display: flex;
gap: 1rem;
align-items: center;
padding: 1rem;
transition: all 0.2s;
}
.card:hover {
border-color: var(--primary);
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0,0,0,0.3);
}
.avatar-80 {
width: 80px;
height: 80px;
border-radius: var(--radius);
border: 2px solid var(--border);
overflow: hidden;
}
.info-section {
flex: 1;
min-width: 0; /* Allow text truncation */
}
Age Calculation
Dynamic age display from birth date:
const calculateAge = (birthDate) => {
const today = new Date()
const birth = new Date(birthDate)
let years = today.getFullYear() - birth.getFullYear()
let months = today.getMonth() - birth.getMonth()
if (months < 0) {
years--
months += 12
}
// Format: "2y 3mo" or "8mo" or "3y"
if (years === 0) return `${months}mo`
if (months === 0) return `${years}y`
return `${years}y ${months}mo`
}
Interactive States
Default
- Border: var(--border)
- Shadow: var(--shadow-sm)
- Transform: none
Hover
- Border: var(--primary)
- Shadow: 0 8px 16px rgba(0,0,0,0.3)
- Transform: translateY(-2px)
- Arrow opacity: 1.0
- Transition: 0.2s cubic-bezier
Active/Click
- Navigate to detail page
- Maintains selection state in history
Responsive Behavior
Desktop (>768px)
- Full horizontal layout
- All metadata visible
- Hover effects enabled
Tablet (768px - 1024px)
- Slightly smaller avatar (70px)
- Abbreviated metadata
- Touch-friendly spacing
Mobile (<768px)
- Avatar: 60px
- Name on top line
- Metadata stacks below
- Registration badge wraps
- Larger tap targets
Accessibility
Keyboard Navigation
- Cards are focusable links
- Tab order follows visual order
- Enter/Space to activate
- Focus ring with primary color
Screen Readers
- Semantic HTML (Link + heading structure)
- Alt text on avatar images
- Icon meanings in aria-labels
- Registration formatted as code
Color Contrast
- Name: High contrast (var(--text-primary))
- Metadata: Medium contrast (var(--text-secondary))
- Icons: Sufficient contrast ratios
- Sex icons: Color + symbol (not color-only)
Benefits
User Experience
- Faster Scanning - See 3x more dogs without scrolling
- Quick Comparison - All key info visible at once
- Less Cognitive Load - Consistent layout, predictable
- Better Navigation - Clear visual hierarchy
Performance
- Smaller Images - Avatar size reduces bandwidth
- Lazy Loading - Efficient with IntersectionObserver
- Less Rendering - Simpler DOM structure
- Faster Scrolling - Fewer pixels to paint
Mobile
- Touch Targets - Full card width clickable
- Vertical Real Estate - More content on screen
- Thumb-Friendly - No precise tapping required
- Data Efficient - Smaller photo downloads
Usage Context
Dashboard
- Shows 6-8 recent dogs
- "View All" button to Dogs page
- Provides quick overview
Dogs List
- Full searchable/filterable catalog
- Horizontal scroll on mobile
- Infinite scroll potential
- Batch operations possible
NOT Used For
- Dog detail page (uses full photo gallery)
- Pedigree tree (uses compact nodes)
- Print layouts (uses different format)
Future Enhancements
Planned
- Checkbox selection mode (bulk actions)
- Drag-to-reorder in custom lists
- Quick actions menu (edit, delete)
- Photo upload from card
- Inline editing of name/breed
Considered
- Multi-select with Shift+Click
- Card density options (compact/comfortable/spacious)
- Alternative views (grid toggle)
- Column sorting (name, age, breed)
- Grouping (by breed, age range)
Examples
Example 1: Male with Photo
┌───────────────────────────────────────────────────────────┐
│ [Photo] Max ♂ → │
│ Golden Retriever • 2y 3mo • Golden │
│ #AKC-SR123456 │
└───────────────────────────────────────────────────────────┘
Example 2: Female No Photo
┌───────────────────────────────────────────────────────────┐
│ [🐶] Bella ♀ → │
│ icon Labrador Retriever • 8mo • Black │
└───────────────────────────────────────────────────────────┘
Example 3: Puppy No Registration
┌───────────────────────────────────────────────────────────┐
│ [Photo] Rocky ♂ → │
│ German Shepherd • 3mo │
└───────────────────────────────────────────────────────────┘
Last Updated: March 8, 2026