DEV Community

Cover image for Cardify: restyling a storefront's product cards without touching its code
Aniq Haider
Aniq Haider

Posted on Originally published at aniq-haider.vercel.app

Cardify: restyling a storefront's product cards without touching its code

Every merchant eventually asks the same thing: "Can our product cards look different?"

On a commerce platform, that request is more expensive than it sounds. A product card is not one component. It shows up in collection grids, carousels, search results and related-product rows, and each of those widgets renders its own markup. Changing the look of a card means touching all of them, for every storefront that wants it.

I built Cardify to avoid that. It is an app that restyles product cards from the outside. The merchant opens an admin screen, flips a switch, picks one of five designs, and the storefront's own cards are replaced live. No storefront redeploy, no page reload, no edits to the widgets that render the cards.

It is now running in production on 16 storefronts on the Ratio platform.

How a card gets replaced

The storefront exposes only a few data attributes on its native cards. Everything else is loaded on demand. There are three flows to understand: setup, what happens on the storefront, and what happens when a shopper clicks.

SETUP (merchant)
  Merchant admin ──▶ Settings API ──▶ Public config
  React + Vite       Fastify + MySQL   Read-only endpoint
  switch on, pick    admin writes are  the storefront
  design + colours   key-protected     loader calls

ON THE STOREFRONT (every page load)
  Tiny loader ──▶ Config says off? ──yes──▶ do nothing, native cards stay
  0.5 KB gzip          │
  fetches config       no
                       ▼
                  Load widget ──▶ Overlay
                  ~10 KB gzip,    hide each native card,
                  imported lazily mount a Cardify card in its cell

WHEN A SHOPPER CLICKS
  Cardify card ──▶ Hidden native link ──▶ Store behaviour runs
  click or         click is delegated     routing, quick-buy popup
  quick-buy        to the store's own     and analytics, unchanged
                   element
Enter fullscreen mode Exit fullscreen mode

The important part is the second flow. The loader is tiny, about half a kilobyte gzipped, and its only job is to fetch the merchant's config. If Cardify is switched off, it does nothing and the shopper never downloads anything else. If it is on, the widget, about 10 KB gzipped, is imported lazily, hides each native card and mounts a Cardify card in the same cell.

Design choices that matter

  • Purely cosmetic. A Cardify card uses exactly the data the native card already renders. There is no new data contract for the storefront to maintain, which is a big part of why it can be dropped onto a store without a conversation with that store's developers.
  • Native cards are hidden, never removed. The overlay mounts inside the same cell, so the store's grid and carousel layout stays untouched. A MutationObserver picks up cards that appear later, such as items loaded by infinite scroll or a filter change.
  • Behaviour is delegated back to the host. When a shopper clicks a Cardify card, it clicks the hidden native link. The store's own routing, quick-buy popup and tracking keep running exactly as before. Cardify never has to reimplement any of it.
  • It fails safe. If the backend is down, the config call errors or the bundle is blocked, the shopper simply sees the original cards. The worst case is "Cardify did nothing", never a broken product grid.
  • No polling. The config refreshes on page load and when the tab regains focus. A timer would make every visitor's open tab hit the backend, for no benefit.
  • The server refuses to start without an admin key. A missing environment variable can never silently mean an open admin.

Five designs, one structure

The five designs are classic, minimal, overlay, hover-reveal and editorial. They all share one DOM structure and differ only in CSS. That is the reason click and quick-buy bridging works for every design for free: the elements the bridge looks for are in the same place in all of them.

Merchants can tune accent, text and background colours, corner radius and image shape, and toggle the badge, quick-buy, rating and compare-at price. Every setting is validated with a shared zod schema, used by both the admin and the API, so the two cannot drift apart.

Adding a sixth design is a registry entry plus one CSS block.

Status

Cardify is live in production on 16 storefronts. It is covered by unit tests and a headless-Chromium end-to-end suite that runs the real overlay against mocked storefront pages.

The stack: Lit for the storefront widget, Fastify and MySQL for the API, React and Vite for the admin, and Playwright for the end-to-end tests.

If you are building something that has to live inside someone else's page, the lesson I would pass on is this: hide, don't remove, delegate instead of reimplementing, and make every failure path end in "the original page, unchanged".

Source code: a lightweight, self-contained version is on GitHub: AniqHaider/cardify-lite.

Originally published on my portfolio.

Top comments (0)