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 Type | Component | Props/Keys |
|---|---|---|
| Loading skeleton | ProductComparisonSkeleton | variant="compare" |
| Empty state | CompareEmptyState | productName, onGetAlert, onSuggestRetailer, onBack |
| Partial banner | StaleDataBanner (reused) | variant="partial", availableCount, selectCount |
| Stale banner | StaleDataBanner | staleCount, oldestTimestamp, onRefresh |
| Error state | ErrorState | onRetry, onContactSupport |
| Freshness badge | FreshnessBadge | freshness: "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"andaria-live="polite" - Stale badges:
aria-label="Price verified X days ago" - Affiliate indicator:
aria-label="Affiliate link"with tooltip description - Refresh buttons:
aria-describedbylinking to timestamp of last refresh
6. Quick Reference: All Compare Surface Strings
| Key | String |
|---|---|
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 Range | Label | What It Means |
|---|---|---|
| 95–100% | Highly Confident | Exact match by brand + model + title |
| 80–94% | Likely Match | Strong title/feature alignment |
| 60–79% | Possible Match | Similar product — verify before buying |
| Below 60% | Uncertain | Different 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
| Key | String |
|---|---|
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