← Back to documentation

compare-surface-microcopy

Compare-Surface Reassurance & Disclosure Microcopy

Overview

This document provides microcopy for all compare-surface states where users need clarity on data freshness, ranking logic, and affiliate context. Tone is honest, specific, and trust-forward — never defensive or vague.


1. Compare State Microcopy

1.1 Loading State

Banner: (none — skeleton is self-explanatory)

Skeleton labels:

  • "Loading prices..."
  • "Checking availability..."
  • "Fetching retailer data..."

Tone guidance: Neutral, brief. No reassurance needed — loading states are expected.


1.2 Empty State (No Matches Found)

Heading: "No retailers found"

Body: "We couldn't find this product at other retailers right now. This product may be exclusive to [retailer name], or it could be new to the market."

Actions:

  • "Get Price Alert" — primary
  • "Suggest a Retailer" — secondary
  • "Back to Search" — tertiary

Tone guidance: Acknowledge exclusivity possibility without discouraging. Avoid "error" language.


1.3 Partial State (Fewer Retailers Than Expected)

Banner heading: "Showing available retailers only"

Banner body: "Only [X] retailer(s) have this product in our catalog. We're continuously adding new sources."

Pill controls: Dim 3rd/4th options with aria-disabled="true" and tooltip: "Not enough data for [N]-way comparison"

Tone guidance: Frame limitation as catalog growth story, not a failure. Be specific with the count.


1.4 Stale Data State

Banner heading: "Price data may be outdated"

Banner body (single retailer stale): "Price from [retailer] was last checked [X] days ago. [Refresh prices]"

Banner body (multiple stale): "Some prices haven't been verified recently. Last checked [X] days ago for [retailer A], [Y] days ago for [retailer B]. [Refresh all prices]"

Per-retailer freshness badge: Subtle amber ⚠️ Xd next to stale prices

Refresh button: "Refresh Prices" or "Update Prices"

Tone guidance: Be honest about age. Specific days build trust more than vague "outdated" labels. Always offer a path forward (refresh).


1.5 Error State

Heading: "Couldn't load comparison"

Body: "Something went wrong while fetching prices. This is usually temporary — please try again."

Actions:

  • "Try Again" — primary
  • "Contact Support" — secondary (links to /feedback)

Tone guidance: Normalize the error. Don't use "failed" or "error" as nouns in the heading.


2. Freshness Disclosure Microcopy

2.1 Fresh Data (< 24 hours)

Badge: (none — implicit default)

Timestamp line (optional): "Prices verified today"

2.2 Recent Data (24h – 7 days)

Badge: (none — subtle treatment acceptable)

Timestamp line (optional): "Prices verified within the last week"

2.3 Stale Data (7 – 30 days)

Badge: ⚠️ Xd (amber)

Footer disclosure: "Price verified [X] days ago. [Refresh for current price]"

2.4 Very Stale Data (> 30 days)

Badge: ⚠️ Xd (red)

Footer disclosure: "This price was last checked [X] days ago. [Refresh for current price]"

Tone guidance: For very stale, consider showing original price vs. current market range if available, to set expectations.


3. Ranking Disclosure Microcopy

3.1 Price Ranking

Default label on sorted column: "Price: Low to High" / "Price: High to Low"

Rank badge on product: "#1 cheapest" or "Cheapest" — avoid ordinal abuse (don't say "#1" unless truly #1)

Savings callout (when applicable): "Save S$[X] vs. average price" or "S$[X] below market average"

3.2 "Best Deal" Designation

Criteria callout (hover/tooltip): "Based on price, availability, and seller rating"

Empty state for no deal: (don't show "best deal" widget if < 2 products or all unavailable)

3.3 Affiliate Disclosure

Surface placement: Footer of compare table, below all products

Disclosure text (SG): "BuyWhere uses affiliate links. We may earn a commission when you purchase through retailer links, at no extra cost to you. This helps support our free price-comparison service."

Short form (tooltip on affiliate indicator): "Affiliate link — we may earn a commission"

Placement in compare card: Small commissions or 💰 indicator near buy_url button, with hover disclosure

Tone guidance: Be explicit about affiliate relationship. "At no extra cost to you" reduces friction. Frame it as supporting a free service, not a warning.


4. Trust-Sensitive Tone Guidance

Do:

  • Be specific: "Last checked 8 days ago" > "outdated"
  • Be honest: Never hide stale data — show freshness badges prominently
  • Offer action: Always pair a problem statement with a solution ("Refresh", "Get Alert", "Suggest")
  • Use plain language: "We couldn't find prices" > "No results returned"
  • Normalize variability: "Prices change often" is better than implying static data

Don't:

  • Don't use alarm language for stale data — "⚠️ Xd" is sufficient
  • Don't promise accuracy you can't guarantee — always include refresh/action paths
  • Don't bury affiliate disclosures — visible placement builds trust
  • Don't use technical jargon in user-facing copy ("data_freshness", "cache_hit", "stale_count")
  • Don't say "error" as a noun in headings — use "Couldn't" or "Something went wrong"

5. Reuse Notes for Frontend & Design

Component Mapping

Microcopy TypeComponentProps/Keys
Loading skeletonProductComparisonSkeletonvariant="compare"
Empty stateCompareEmptyStateproductName, onGetAlert, onSuggestRetailer, onBack
Partial bannerStaleDataBanner (reused)variant="partial", availableCount, selectCount
Stale bannerStaleDataBannerstaleCount, oldestTimestamp, onRefresh
Error stateErrorStateonRetry, onContactSupport
Freshness badgeFreshnessBadgefreshness: "fresh" | "recent" | "stale" | "very_stale", lastChecked: ISO string
Affiliate indicator(inline)affiliate_url present → show 💰 with tooltip
Ranking callout(inline or PriceRankBadge)rank, total, savings

i18n Keys to Create

compare.loading = "Loading prices..."
compare.empty.heading = "No retailers found"
compare.empty.body = "We couldn't find this product at other retailers right now."
compare.partial.banner = "Showing available retailers only"
compare.stale.banner = "Price data may be outdated"
compare.error.heading = "Couldn't load comparison"
compare.error.body = "Something went wrong while fetching prices."
compare.freshness.verified_today = "Prices verified today"
compare.freshness.verified_recent = "Prices verified within the last week"
compare.affiliate.disclosure = "BuyWhere uses affiliate links."
compare.affiliate.tooltip = "Affiliate link — we may earn a commission"
compare.action.refresh = "Refresh Prices"
compare.action.get_alert = "Get Price Alert"
compare.action.suggest_retailer = "Suggest a Retailer"

Accessibility Notes

  • All banners: role="alert" and aria-live="polite"
  • Stale badges: aria-label="Price verified X days ago"
  • Affiliate indicator: aria-label="Affiliate link" with tooltip description
  • Refresh buttons: aria-describedby linking to timestamp of last refresh

6. Quick Reference: All Compare Surface Strings

KeyString
compare.loading"Loading prices..."
compare.empty.heading"No retailers found"
compare.empty.body"We couldn't find this product at other retailers right now. This product may be exclusive to one retailer, or it could be new to the market."
compare.partial.banner"Only [X] retailer(s) have this product. Showing all available options."
compare.stale.banner_single"Price from [retailer] was last checked [X] days ago."
compare.stale.banner_multi"Some prices haven't been verified recently. Last checked [X] days ago."
compare.error.heading"Couldn't load comparison"
compare.error.body"Something went wrong while fetching prices. This is usually temporary — please try again."
compare.action.refresh"Refresh Prices"
compare.action.get_alert"Get Price Alert"
compare.action.suggest_retailer"Suggest a Retailer"
compare.action.back"Back to Search"
compare.freshness.verified_today"Prices verified today"
compare.freshness.verified_recent"Prices verified within the last week"
compare.affiliate.disclosure"BuyWhere uses affiliate links. We may earn a commission when you purchase through retailer links, at no extra cost to you."
compare.affiliate.tooltip"Affiliate link — we may earn a commission"
compare.rank.cheapest"Cheapest"
compare.rank.save_vs_avg"Save S$[X] vs. average"
compare.rank.badge_tooltip"Based on price, availability, and seller rating"


7. Trust-State Microcopy

7.1 Trust Score Badges

Highly Confident badge (95–100%):

"Highly Confident" — no additional label needed; match quality is implicit

Likely Match badge (80–94%):

"Likely Match" or "Strong Match"

Possible Match badge (60–79%):

"Possible Match" with amber indicator

Uncertain badge (Below 60%):

"Verify Before Buying" with red indicator


7.2 Trust Score Legend (Accessible Table)

Header: "What do the match labels mean?"

Score RangeLabelWhat It Means
95–100%Highly ConfidentExact match by brand + model + title
80–94%Likely MatchStrong title/feature alignment
60–79%Possible MatchSimilar product — verify before buying
Below 60%UncertainDifferent product — check carefully

7.3 Low Trust Warning Banner

Heading: "Low confidence match detected"

Body (single product uncertain): "We're not certain this is the same product. [Retailer]'s listing may differ from your search. Verify details before buying."

Body (mixed trust in comparison): "Some products in this comparison have lower match confidence. Look for the match label on each result."

Actions:

  • "View Details" — primary
  • "Report Mismatch" — secondary
  • "Continue Anyway" — tertiary (only for 60-79% range)

Tone guidance: Be direct about uncertainty without being alarmist. Offer verification path. Never block users, just inform.


7.4 Trust Score Breakdown (Detailed View)

Heading: "Trust Score Explained"

Body: "We evaluate three factors:"

Factor 1: "Title match — How closely the product name matches your search"

Factor 2: "Image similarity — AI comparison of product images"

Factor 3: "Price reasonability — Within expected range for this product"

Footer CTA: "Report incorrect match →"


7.5 Trust State in Comparison Card

Inline badge (highly confident): (none — implicit default)

Inline badge (likely match): "✓ Strong match"

Inline badge (possible match): "⚠️ Verify match"

Inline badge (uncertain): "⚠️ Check carefully"

Tooltip for uncertain: "This listing may differ from your search. Confirm details (brand, model, specifications) before purchasing."


7.6 Mixed Trust Comparison Banner

Heading: "Match confidence varies"

Body: "This comparison includes products with different confidence levels. Look for the match label on each card."

Pill control treatment: Dim uncertain options with aria-disabled="true" and tooltip: "Low confidence — verify before buying"


7.7 Trust Alert in Price Comparison

Alert banner (when filtering by trust): "Showing products with [filter level] or higher match confidence. [Show all results]"

Filter tooltip: "Filter by how confident we are that the product matches your search"


8. Quick Reference: Trust-State Strings

KeyString
compare.trust.highly_confident(none — implicit)
compare.trust.likely_match"Likely Match"
compare.trust.possible_match"Possible Match"
compare.trust.uncertain"Verify Before Buying"
compare.trust.legend.header"What do the match labels mean?"
compare.trust.low_warning.heading"Low confidence match detected"
compare.trust.low_warning.body"We're not certain this is the same product."
compare.trust.mixed.heading"Match confidence varies"
compare.trust.mixed.body"This comparison includes products with different confidence levels."
compare.trust.action.view_details"View Details"
compare.trust.action.report_mismatch"Report Mismatch"
compare.trust.action.continue_anyway"Continue Anyway"
compare.trust.action.verify_details"Verify Details"
compare.trust.filter.show_all"Show all results"
compare.trust.breakdown.title"Trust Score Explained"
compare.trust.breakdown.factor1"Title match"
compare.trust.breakdown.factor2"Image similarity"
compare.trust.breakdown.factor3"Price reasonability"
compare.trust.breakdown.report"Report incorrect match"

Document version: 1.1 | Created for BUY-2661 | Extended for BUY-2674 (trust-state microcopy) | For questions, tag @copy in /PAP/issues/BUY-2674