← Back to documentation

compare-table-compact-row-layout

Compare-Table Compact Row Layout Spec

Context

BUY-2695: Prototype compare-table compact row layout

AI agents comparing products frequently encounter partial merchant coverage — some retailers don't list certain products, have blocked scrapers, or have prices that fluctuate rapidly. The compare-table UI must remain readable and trustworthy even when 30–50% of expected merchant data is absent.


1. Row Anatomy

1.1 Standard Row (Full Data)

┌─────────────────────────────────────────────────────────────────────────────┐
│ [ATTR LABEL]  │  [MERCHANT A]      │  [MERCHANT B]      │  [MERCHANT C]  │
│               │  $49.90 SGD       │  $52.00 SGD        │  —             │
└─────────────────────────────────────────────────────────────────────────────┘
  • Attribute label column (fixed left): 120px min-width, left-aligned
  • Merchant value columns (fluid): equal width distribution, content centered
  • Row height: 48px standard, 56px when content wraps

1.2 Compact Row Variants

Price Row (Most Important — Always Prominent)

┌─────────────────────────────────────────────────────────────────────────────┐
│ Price ★     │  $49.90 SGD ▼      │  $52.00 SGD       │  —               │
│ (48px)      │  CHEAPEST          │                   │  [greyed "—"]   │
└─────────────────────────────────────────────────────────────────────────────┘

Availability Row

┌─────────────────────────────────────────────────────────────────────────────┐
│ Stock       │  ● In Stock       │  ○ Low Stock      │  — unavailable    │
└─────────────────────────────────────────────────────────────────────────────┘

Rating Row

┌─────────────────────────────────────────────────────────────────────────────┐
│ Rating      │  ★★★★☆ (142)     │  ★★★★★ (89)       │  —               │
└─────────────────────────────────────────────────────────────────────────────┘

2. Missing-Data Cell States

2.1 Visual State Matrix

StateVisual TreatmentCSS ClassExample
Data presentNormal display.cell--present$49.90 SGD
Price missingEm-dash + grey text.cell--missing
Zero priceShows "$0.00" with free badge.cell--zero$0.00 FREE
Stale dataAmber tint + ⚠ badge.cell--stale$49.90 ⚠ 8d
Very staleRed tint + ⚠ badge.cell--very-stale$49.90 ⚠ 35d
Merchant errorRed border + error icon.cell--error⚠ unavailable

2.2 Missing Price States

Price Missing (retailer doesn't list product)

Merchant C
  — unavailable
  • Grey italic text
  • Tooltip: "Retailer does not carry this product"

Price Missing (scrape error / timeout)

Merchant C
  — timeout
  • Grey text with clock icon
  • Tooltip: "Price temporarily unavailable. [Refresh]"

Price Missing (price not disclosed by retailer)

Merchant C
  — no price
  • Grey text with lock icon
  • Tooltip: "Retailer does not disclose prices"

2.3 Data Freshness Indicators

Appended to cell value when data_freshness is set:

FreshnessBadgeColorExample
fresh (<24h)nonedefault$49.90
recent (24h–7d)nonedefault$49.90
stale (7–30d)⚠ Xdamber$49.90 ⚠ 12d
very_stale (>30d)⚠ Xdred$49.90 ⚠ 45d

3. Partial Coverage Row Behavior

3.1 Column Collapse vs. Show-Gap

When some merchants don't have data for a product, two approaches:

Option A: Show-Gap (Preferred)

  • All merchant columns remain visible
  • Missing cells show placeholder state
  • Row label shows "×3 merchants" coverage indicator

Option B: Column Collapse

  • Only merchants with data are shown
  • Coverage banner: "Showing 2 of 4 retailers"

Recommendation: Use Option A (show-gap) for AI agent contexts where the user needs to see the full merchant landscape. Use Option B for end-user retail contexts where empty columns create noise.

3.2 Coverage Indicator

Append to row label when not all merchants have data:

Price ★ (3/4 merchants)
Stock (2/4 merchants) ← amber indicator
Rating (4/4 merchants)

Format: (present_count/total_count) appended to label

3.3 "No Data" Row Suppression

For spec rows where only 1 merchant has data:

  • Row is demoted to secondary prominence
  • Shown with amber "Limited data" badge
  • Moved below primary spec rows

4. Compact Row CSS Layout

4.1 Grid Structure

.compare-table {
  display: grid;
  grid-template-columns: 120px repeat(var(--merchant-count), 1fr);
  /* Label column fixed, merchant columns fluid and equal */
}

.row--price {
  grid-column: 1 / -1; /* Price spans full width visually */
  /* But data still aligned to merchant columns */
}

.row--price .attr-label {
  grid-column: 1;
}

.row--price .value-cell {
  grid-column: span 1; /* Each value in its merchant column */
}

4.2 Compact Mode (Toggle)

When compact=true:

.compare-table--compact {
  --row-height: 36px;
  --label-width: 80px;
  font-size: 13px;
}

.compare-table--compact .value-cell {
  padding: 4px 8px; /* Reduced padding */
}

4.3 Mobile Collapsed View

On screens < 640px:

  • Table becomes card stack
  • Each merchant becomes a horizontal card
  • Price prominently displayed at top of each card

5. Row Priority Order

Display rows in this priority order (top = highest):

  1. Price — always first, always prominent
  2. Availability — in-stock status
  3. Rating — aggregate rating + count
  4. Shipping — delivery time/cost if available
  5. Trust badges — verified authentic, secure checkout, returns
  6. Merchant name — source attribution
  7. Brand — brand when not in product name
  8. Specs — product-specific attributes (color, size, storage, etc.)
  9. URL link — CTA to purchase

Rows 1–4 are primary (always shown). Rows 5–9 are secondary (collapsible on mobile, or hidden behind "Show more specs").


6. Empty / Low-Coverage Behavior

6.1 Full Empty State (0 merchants)

Show LowCoverageState component (already exists at /components/LowCoverageState.tsx).

6.2 Low Coverage State (1–2 merchants)

┌──────────────────────────────────────────────────────────────┐
│  ⚠ Limited price data                                        │
│  Showing prices from 2 retailers. [Suggest a retailer]       │
└──────────────────────────────────────────────────────────────┘

Display the 1–2 available merchants with full data. Show coverage indicator on each row.

6.3 Partial Coverage with Stale Data

When >50% of visible data is stale:

┌──────────────────────────────────────────────────────────────┐
│  ⚠ Some prices are outdated                                 │
│  Last verified 12 days ago for 2 retailers. [Refresh all]   │
└──────────────────────────────────────────────────────────────┘

7. Handoff Notes for Implementation

7.1 Data Shape

The component receives CompareMatch[] with these key fields for row rendering:

interface CompareRowData {
  merchantId: string;
  merchantName: string;
  price: Decimal | null;
  priceMissing: boolean;
  priceMissingReason?: 'retailer_api_error' | 'retailer_timeout' | 'product_unavailable' | 'price_not_disclosed' | 'crawl_error';
  dataFreshness?: 'fresh' | 'recent' | 'stale' | 'very_stale';
  isAvailable: boolean;
  rating?: number;
  reviewCount?: number;
  affiliateUrl?: string;
  buyUrl: string;
}

7.2 Accessibility

  • All missing cells have aria-label with explicit reason: "Price unavailable from [merchant]"
  • Stale indicators: aria-label="Price verified X days ago"
  • Coverage indicators: aria-label="Showing 3 of 4 merchants"
  • Color is not sole indicator — always pair with icon or text

7.3 Performance

  • Use CSS content-visibility: auto for rows outside viewport
  • Lazy-load merchant images below fold
  • Debounce refresh actions (500ms)

7.4 State Management

Reference hooks/useCompareCardState.ts pattern from docs/compact-compare-card-state.md for selection state. This layout spec does not change state management — only visual row structure.


8. Out of Scope

  • Selection/checkbox behavior (handled by ProductComparison.tsx)
  • Add-to-cart or direct purchase flows
  • Sorting or filtering UI
  • Export functionality

9. Related Documents

  • docs/compact-compare-card-state.md — state machine for compare card selection
  • docs/compare-surface-microcopy.md — all microcopy strings for compare surfaces
  • docs/DESIGN_TRUST_BADGES_AND_FRESHNESS.md — trust and freshness badge specs
  • app/schemas/product.pyCompareMatch, PriceMissingReason schema definitions

Spec version: 1.0 | Created for BUY-2695 | Frontend handoff reference