figma guide
Designing recently viewed products UI in Figma: rails, persistence, and handoff
Design recently viewed product rails, homepage strips, and empty states in Figma with local vs server persistence, card reuse, and Dev Mode specs for PDP recirculation.
- Published
- Updated
- Jun 26, 2026
- Read time
- 6 min
- Level
- Beginner
Quick answer
Recently viewed products help shoppers return to items they already inspected without relying on browser back or search. Show a horizontal scroll rail of product cards (usually 4–12 items, newest first) on the homepage, cart empty state, PDP footer, or account dashboard. Persist the list in local storage for guests and account history for logged-in users—cap at 20 SKUs and dedupe so the same PDP visit does not create duplicate cards. Hide the module when the list is empty. Start from the Figma guides hub.
Who this is for
- Product designers improving return visits and reducing “where was that product?” friction.
- Design system teams reusing PLP card components in a slim
RecentlyViewedCardvariant. - Engineers syncing view events from PDP, handling GDPR consent, and merging guest → logged-in history on sign-in.
Where recently viewed appears
| Surface | Component | Purpose |
|---|---|---|
| Homepage | RecentlyViewedRail | Resume session after tab close |
| PDP below buy box | Same rail, compact | Cross-sell without algorithm |
| Empty cart | Rail + CTA | Fill cart from history |
| 404 / search no results | Optional rail | Recovery path |
| Account dashboard | Full grid | Longer history, remove item action |
| Checkout confirmation | Usually skip | Use recommendations instead |
Verdict: recently viewed is behavioral recall, not personalized ML. Keep it separate from “You may also like” so analytics and merchandising rules stay clear.
RecentlyViewedRail anatomy
| Part | Spec | Notes |
|---|---|---|
| Section header | ”Recently viewed” + optional “Clear all” | Sentence case; not “RECENTLY VIEWED” |
| Card track | Horizontal scroll, snap optional | Same width as recommendations row |
| Card | Image, title, price, optional sale badge | Reuse PLP card at 160–200px width |
| Nav arrows | Desktop only when overflow | Hide on touch devices |
| Empty state | Hidden section or placeholder | No rail DOM when length = 0 |
| Privacy link | Footer microcopy optional | ”Based on your browsing on this device” |
RecentlyViewedRail
├── Variant: density=compact | standard
├── Variant: state=populated | empty | loading
├── Property: maxItems=12
├── Property: showClearAll=true | false
└── Layers:
├── SectionHeader
├── ScrollTrack
│ └── RecentlyViewedCard × n
└── NavArrowPrev / NavNext
Use Auto Layout horizontal gap 12–16px. Cards should not shrink below min width—overflow scrolls instead of squashing.
Card content comparison
| Field | Recently viewed card | Full PLP card |
|---|---|---|
| Image | Yes | Yes |
| Title | 2-line clamp | 2-line clamp |
| Price | Current + sale | Same pricing tokens |
| Rating | Optional | Often shown on PLP |
| Quick view | Optional | Common on PLP |
| Wishlist | Optional icon | Common |
| Variant swatches | Usually hidden | Sometimes shown |
| Remove (×) | Account view only | N/A |
Rule: price and availability must be live at render—stale sale prices in history erode trust. Show out-of-stock overlay if SKU unavailable.
Persistence and privacy
| User type | Storage | Merge on login |
|---|---|---|
| Guest | localStorage / cookie | Push to account API on auth |
| Logged in | Server-side list | Dedupe by product ID, keep newest |
| GDPR / consent | Block until analytics consent | Document in legal frame |
| Incognito | Session only | Clear on tab close |
| B2B catalog | May disable for shared devices | Product setting |
Handoff note: dedupe on view—re-visiting PDP moves item to front, does not duplicate. Cap list length server-side to prevent unbounded payloads.
Layout comparison
| Placement | Best for | Design notes |
|---|---|---|
| Homepage mid-page | High traffic stores | Below hero, above categories |
| PDP accordion footer | Long consideration | Collapse when only 1 item |
| Cart drawer footer | Impulse add | Max 4 cards visible |
| Full-width band | Desktop homepage | Match product listing grid gutters |
| Sidebar widget | Legacy layouts | Avoid on mobile |
On mobile PDP, place the rail below reviews and above recommendations—do not push primary buy box below fold.
Interaction states
| State | Trigger | UI |
|---|---|---|
| Hidden | Zero items in history | Section not rendered |
| Loading | First paint hydrating storage | Skeleton cards × 4 |
| Populated | ≥1 view event | Full rail |
| Item removed | User taps × on account page | Animate out; persist |
| Clear all | Header link | Confirm modal optional |
| Stale product | Discontinued SKU | Gray card + “No longer available” |
Prototype populated → remove one item → clear all on account dashboard frame.
Sync with other modules
| Module | Relationship |
|---|---|
| Recommendations | Separate section; do not interleave cards |
| Compare products | Card may include compare checkbox |
| Wishlist | Independent heart state |
| Quick view | Optional on rail cards |
| Search | History does not replace search |
If the same product appears in both recently viewed and recommendations, allow duplicate—different sections, different intent. Do not dedupe across sections in UI unless product asks explicitly.
Handoff checklist
| Item | Dev Mode annotation |
|---|---|
| View event | Fire on PDP product_view with id + timestamp |
| List API | GET /recently-viewed or client-only merge |
| Max items | Default 12 display, 20 stored |
| Sort | viewedAt descending |
| Card click | Navigate to PDP; same tab |
| Image | Use current catalog thumbnail URL |
| Price API | Fresh on each rail render |
| Analytics | recently_viewed_impression, recently_viewed_click, recently_viewed_clear |
| a11y | Region label aria-label="Recently viewed products" |
| Reduced motion | No auto-scroll carousel |
Use Dev Mode checklist and responsive breakpoints for card count at 375 / 768 / 1280px.
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Showing empty rail | Wasted space | Hide section when length = 0 |
| Stale sale prices | Checkout surprise | Fetch live price on render |
| Duplicates after re-view | Clutter | Move-to-front dedupe |
| Same module as recommendations | Analytics blur | Separate headings and APIs |
| Huge history on shared tablet | Privacy | Session cap + clear control |
| Autoplay scroll | Motion sickness | Manual scroll only |
| Full PLP card in narrow rail | Overflow | Slim RecentlyViewedCard variant |
Recommended workflow
- Finalize PLP
ProductCardcomponent with price and image tokens. - Create
RecentlyViewedCardslim variant (drop swatches, optional quick view). - Build
RecentlyViewedRailwith header, track, and desktop arrows. - Design empty (hidden) and loading skeleton states.
- Place rails on homepage, PDP footer, and empty cart frames.
- Add account “Browsing history” page with remove + clear all.
- Spec view event, dedupe, and cap in Dev Mode.
- Prototype scroll and card click → PDP on mobile frame.
FAQ
Recently viewed vs recommendations?
Recently viewed = SKUs the user already opened. Recommendations = algorithm or merchandising rules. Never label one as the other.
Show on checkout?
Usually no—checkout should minimize distraction. Exception: abandoned cart recovery emails, not live checkout UI.
Include in quick view?
Quick view opens from PLP; viewing still counts toward history. No need for a mini rail inside quick view.
GDPR: is this personal data?
Browsing history can be personal data in the EU. Gate persistence behind consent and offer clear in account settings.
Next steps
- Design product recommendations UI in Figma — algorithmic rows vs history
- Design cards in Figma — base card for the rail
- Design product listing and grid UI in Figma — card sizing consistency
- Design empty states in Figma — cart empty + history
- Design shopping cart and checkout UI in Figma — empty cart placement
§ Keep reading