Wishlist For Apparel - JS APIs

Extend the standard wishlist page with a per-card variant selector so shoppers can pick size and colour before adding to cart.

Wishlist Page - Apparel Extensions

Apparel shoppers often save items before deciding on a size or colour. This guide adds one thing to the base Wishlist Page implementation:

  • Variant selector (<select>) on each product card, built from Shopify's product JSON endpoint, letting the shopper pick a size or colour before adding to cart

The selected variant drives the cart add call. The selection is kept in a per-card currentEpi closure variable and is not persisted back to the wishlist.

Already have the base setup? Only renderProducts needs replacing -- see Modified renderProducts. Everything else (initWishlistPage, setupListSwitcher, setupShareButton, the share modal, save nudge, login nudge) is unchanged, including the sessionStorage cache optimisation: initWishlistPage renders immediately from cache, then swat.fetchLists always re-renders from the authoritative API response. Starting from scratch? Copy the Complete Implementation directly -- it contains every file you need.

What Changes

Base guideApparel extension
renderProductsAsync; fetches Shopify product JSON via productJSONCache; renders a cart and remove button per cardAlso reads shopifyProduct.variants to build a variant <select> per card; tracks currentEpi per card so the cart add always uses the shopper's last-selected variant; re-evaluates stock state and cart button when the selection changes

Nothing else in the base guide changes. productJSONCache, fetchProductJSON, initWishlistPage, setupListSwitcher, setupShareButton, the share modal, save nudge, and login nudge are all unchanged.

Modified renderProducts

Replace renderProducts in assets/swym-wishlist-page.js with the version below. The code block is identical to the renderProducts function inside the Complete Implementation — no need to reconcile between them. The apparel version differs from the base guide in three ways:

  1. Variant data -- allVariants is read from shopifyProduct.variants. The variant matching product.epi sets the initial stock state, displayed price, and selected option. currentEpi starts at product.epi and updates as the shopper changes the select.

  2. Variant <select> -- rendered only when the product has more than one variant. Out-of-stock options are disabled. The change handler updates currentEpi, re-evaluates stock, and syncs the cart button label and disabled state.

  3. Cart add uses currentEpi -- the cart button listener always uses the closure-local currentEpi (not product.epi), so the shopper gets whichever variant they last selected. The remove call still uses the original product.epi because that is how the item is keyed in the Swym list.

// Replaces renderProducts in the base wishlist-page.js

async function renderProducts(swat, lid, products, container, countEl, emptyEl) {
    container.setAttribute('aria-busy', 'true');
    container.innerHTML = '';

    if (!products || products.length === 0) {
      if (emptyEl) emptyEl.style.display = 'block';
      container.setAttribute('aria-busy', 'false');
      return;
    }

    if (emptyEl) emptyEl.style.display = 'none';

    var shopifyData = await Promise.all(products.map(function(product) {
      var key = String(product.epi);
      if (productJSONCache[key]) return Promise.resolve(productJSONCache[key]);
      return fetchProductJSON(product.du)
        .then(function(data) { productJSONCache[key] = data; return data; })
        .catch(function() { return null; });
    }));

    products.forEach(function(product, index) {
      var shopifyProduct = shopifyData[index];
      var allVariants = shopifyProduct && shopifyProduct.variants ? shopifyProduct.variants : null;
      var savedVariant = allVariants
        ? allVariants.find(function(v) { return String(v.id) === String(product.epi); })
        : null;
      var inStock = savedVariant ? savedVariant.available : product.stk !== 0;
      var displayPrice = savedVariant ? savedVariant.price : product.pr;

      var currentEpi = product.epi;

      var title = esc(shopifyProduct && shopifyProduct.title || product.dt || 'Product');
      var href = esc(shopifyProduct && shopifyProduct.url || product.du || '#');
      var imgSrc = esc(shopifyProduct && (shopifyProduct.featured_image || (shopifyProduct.images && shopifyProduct.images[0])) || product.iu || '');
      var variantSelectId = 'swym-variant-' + esc(String(product.epi));

      var card = document.createElement('div');
      card.className = 'swym-wishlist-card';
      card.innerHTML =
        '<a href="' + href + '">' +
          '<img src="' + imgSrc + '" alt="' + title + '" loading="lazy">' +
        '</a>' +
        '<a href="' + href + '">' + title + '</a>' +
        (displayPrice ? '<span>' + esc(formatPrice(displayPrice)) + '</span>' : '') +
        '<div class="swym-wishlist-card-actions">' +
          '<label class="swym-wishlist-variant-label"' +
            (allVariants && allVariants.length > 1 ? ' for="' + variantSelectId + '"' : '') + '>' +
            (allVariants && allVariants.length > 1 ? 'Variant' : '&nbsp;') +
          '</label>' +
          (allVariants && allVariants.length > 1
            ? '<select id="' + variantSelectId + '" class="swym-wishlist-variant-select"' +
                ' aria-label="Select variant for ' + title + '"></select>'
            : '<span class="swym-wishlist-variant-static">' +
                (savedVariant && savedVariant.title && savedVariant.title !== 'Default Title'
                  ? esc(savedVariant.title) : '') +
              '</span>') +
          '<button type="button" class="swym-wishlist-cart-btn"' + (!inStock ? ' disabled' : '') +
            ' aria-label="' + (inStock ? 'Add ' + title + ' to cart' : title + ' is out of stock') + '">' +
            (inStock ? 'Add to Cart' : 'Out of Stock') +
          '</button>' +
          '<button type="button" class="swym-wishlist-remove-btn" aria-label="Remove ' + title + ' from wishlist">×</button>' +
        '</div>';

      var variantSelect = card.querySelector('.swym-wishlist-variant-select');
      var cartBtn = card.querySelector('.swym-wishlist-cart-btn');
      var removeBtn = card.querySelector('.swym-wishlist-remove-btn');

      if (variantSelect && allVariants && allVariants.length > 1) {
        allVariants.forEach(function(variant) {
          var option = document.createElement('option');
          option.value = variant.id;
          option.textContent = variant.title + (variant.available ? '' : ' (Out of Stock)');
          option.selected = String(variant.id) === String(product.epi);
          option.disabled = !variant.available;
          variantSelect.appendChild(option);
        });

        variantSelect.addEventListener('change', function() {
          currentEpi = Number(variantSelect.value);
          var selected = allVariants.find(function(v) { return String(v.id) === String(currentEpi); });
          var nowInStock = selected && selected.available;
          cartBtn.disabled = !nowInStock;
          cartBtn.textContent = nowInStock ? 'Add to Cart' : 'Out of Stock';
          cartBtn.setAttribute('aria-label', nowInStock
            ? 'Add ' + title + ' to cart'
            : title + ' is out of stock');
        });
      }

      cartBtn.addEventListener('click', function() {
        if (cartBtn.disabled) return;
        cartBtn.textContent = 'Adding...';
        cartBtn.setAttribute('aria-label', 'Adding ' + title + ' to cart');
        cartBtn.disabled = true;
        fetch('/cart/add.js', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ items: [{ id: currentEpi, quantity: 1 }] })
        })
        .then(function(response) {
          if (response.ok) {
            cartBtn.textContent = 'Added!';
            cartBtn.setAttribute('aria-label', title + ' added to cart');
            setTimeout(function() {
              cartBtn.textContent = 'Add to Cart';
              cartBtn.setAttribute('aria-label', 'Add ' + title + ' to cart');
              cartBtn.disabled = false;
            }, 2000);
          } else {
            cartBtn.textContent = 'Error';
            cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
            cartBtn.disabled = false;
          }
        })
        .catch(function() {
          cartBtn.textContent = 'Error';
          cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
          cartBtn.disabled = false;
        });
      });

      removeBtn.addEventListener('click', function() {
        swat.deleteFromList(
          lid,
          { epi: product.epi, empi: product.empi, du: product.du },
          function() {
            card.remove();
            var remaining = container.querySelectorAll('.swym-wishlist-card');
            if (countEl) countEl.textContent = '(' + remaining.length + ' items)';
            if (remaining.length === 0 && emptyEl) emptyEl.style.display = 'block';
          },
          function() {}
        );
      });

      container.appendChild(card);
    });

    container.setAttribute('aria-busy', 'false');
  }

currentEpi vs product.epi: product.epi is the variant ID stored in the wishlist. currentEpi starts at that value and updates as the shopper changes the select. The cart add call always uses currentEpi, so the shopper gets whichever variant they last selected - not necessarily the one originally saved.

Remove uses product.epi: The remove call always references the original product.epi because that is how the item is keyed in the Swym list. Changing the variant select does not change which item gets removed.

Styling additions

ElementPurpose
.swym-wishlist-variant-labelLabel above the variant dropdown - small, muted; use theme body font
.swym-wishlist-variant-selectVariant <select> - full card width; inherits theme font and border style
.swym-wishlist-cart-btnAdd to Cart button; disabled state when out of stock or no in-stock variant is selected
.swym-wishlist-remove-btnRemove from wishlist button; ghost or icon style to keep visual weight low

Product Fields and APIs Used (additions)

Field / APIUsage
product.epiVariant ID stored on the wishlist item - used as the initial value of currentEpi and as the initially selected <option>
variants[n].idShopify variant ID - set as the <option> value and passed to /cart/add.js via currentEpi
variants[n].availableStock state - disables out-of-stock options and keeps the Add to Cart button in sync

Complete Implementation

Copy the files for your setup into the theme. The Liquid goes in sections/, the JS and CSS in assets/.

sections/swym-wishlist-page.liquid

{% comment %} Swym Wishlist Page — Apparel {% endcomment %}
<div id="swym-wishlist-page" class="page-width">
  <div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
    <h1><span id="swym-wishlist-title">My Wishlist</span> <span id="swym-wishlist-count"></span></h1>
    <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
      <div id="swym-list-switcher-wrapper" style="display:none;">
        <label for="swym-list-switcher" style="font-size:0.875rem;">List:</label>
        <select id="swym-list-switcher" aria-label="Switch between your wishlists"></select>
      </div>
      <button id="swym-share-btn" style="display:none;" aria-label="Share wishlist">Share Wishlist</button>
    </div>
  </div>

  {%- comment -%} Share modal — rendered hidden; JS controls visibility {%- endcomment -%}
  <div id="swym-share-modal" role="dialog" aria-modal="true" aria-labelledby="swym-share-modal-title" style="display:none; position:fixed; inset:0; z-index:9999;">
    <div id="swym-share-modal-overlay" style="display:block; position:absolute; inset:0; background:rgba(0,0,0,0.5);"></div>
    <div id="swym-share-modal-content" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; padding:2rem; max-width:480px; width:90%; border-radius:8px; max-height:90vh; overflow-y:auto;">
      <div id="swym-share-modal-header">
        <h2 id="swym-share-modal-title">Share Your Wishlist</h2>
        <button id="swym-share-modal-close" aria-label="Close share dialog">&times;</button>
      </div>

      {%- comment -%} Email share form {%- endcomment -%}
      <div class="swym-share-section">
        <label for="swym-share-name-input">Name</label>
        <input id="swym-share-name-input" type="text" placeholder="Your name" aria-label="Your name">

        <label for="swym-share-email-input">Email</label>
        <input id="swym-share-email-input" type="email" placeholder="[email protected]" aria-label="Recipient email address">

        <label for="swym-share-note-input">Message</label>
        <textarea id="swym-share-note-input" placeholder="Check out my wishlist!" aria-label="Message"></textarea>

        <button id="swym-share-email-btn" aria-label="Send wishlist by email">Share Via Email</button>
        <span id="swym-share-email-feedback" style="display:none;" aria-live="polite">Email sent!</span>
      </div>

      {%- comment -%} Social / copy {%- endcomment -%}
      <div class="swym-share-section swym-share-social-section">
        <p class="swym-share-divider-label">or Share via</p>
        <div class="swym-share-social-row">
          <button class="swym-share-social-btn" data-platform="facebook" aria-label="Share on Facebook">Facebook</button>
          <button class="swym-share-social-btn" data-platform="twitter" aria-label="Share on Twitter / X">X</button>
          <button id="swym-share-copy-btn" aria-label="Copy link">Copy Link</button>
        </div>
        <span id="swym-share-copy-feedback" style="display:none;" aria-live="polite">Copied!</span>
      </div>
    </div>
  </div>

  {%- comment -%} Login nudge for anonymous users {%- endcomment -%}
  <div id="swym-login-nudge" style="display:none;">
    <p>Sign in to save your wishlist across devices</p>
    <a href="/account/login?return_url=/pages/wishlist">Sign in</a>
  </div>

  {%- comment -%} Save nudge — shown after 3+ items for anonymous users {%- endcomment -%}
  <div id="swym-save-nudge" style="display:none;" aria-live="polite" aria-atomic="true">
    <p><strong>Don't lose your list!</strong> You've saved <span id="swym-save-nudge-count"></span> items. <a href="/account/login?return_url=/pages/wishlist">Sign in to keep them.</a></p>
    <button id="swym-save-nudge-dismiss" aria-label="Dismiss">&times;</button>
  </div>

  <div id="swym-wishlist-loading" aria-live="polite" aria-atomic="true">Loading your wishlist...</div>
  <div id="swym-wishlist-empty" style="display:none;">
    <p>Your wishlist is empty</p>
    <p>Start adding items you love</p>
    <a href="/collections">Browse products</a>
  </div>
  <div id="swym-wishlist-grid" aria-busy="true" aria-label="Wishlist products"></div>
</div>

<script src="{{ 'swym-wishlist-page.js' | asset_url }}" defer></script>

assets/swym-wishlist-page.js

(function() {
  'use strict';

  var SAVE_NUDGE_THRESHOLD = 3;
  var SS_PRODUCTS_KEY = 'swym-wishlist-products';
  var SS_SHARE_URL_KEY = 'swym-share-url';
  var productJSONCache = {};
  var shareButtonHandler = null;

  function initWishlistPage(swat) {
    var isLoggedIn = swat.platform.isLoggedIn();
    var grid = document.getElementById('swym-wishlist-grid');
    var loading = document.getElementById('swym-wishlist-loading');
    var empty = document.getElementById('swym-wishlist-empty');
    var countEl = document.getElementById('swym-wishlist-count');

    if (!grid) return;

    if (!isLoggedIn) {
      var loginNudge = document.getElementById('swym-login-nudge');
      if (loginNudge) loginNudge.style.display = '';
    }

    var cachedRaw = sessionStorage.getItem(SS_PRODUCTS_KEY);
    var cachedData = null;
    if (cachedRaw) {
      try {
        cachedData = JSON.parse(cachedRaw);
        if (loading) loading.style.display = 'none';
        renderProducts(swat, cachedData.lid, cachedData.products, grid, countEl, empty);
        if (countEl) countEl.textContent = '(' + cachedData.products.length + ' items)';
        setupShareButton(swat, cachedData.lid, cachedData.products.length);
      } catch (e) {
        sessionStorage.removeItem(SS_PRODUCTS_KEY);
        cachedData = null;
      }
    }

    swat.fetchLists({
      callbackFn: function(lists) {
        if (loading) loading.style.display = 'none';

        var cachedLid = cachedData ? cachedData.lid : null;
        var activeList = lists && lists.length
          ? (cachedLid ? (lists.find(function(l) { return l.lid === cachedLid; }) || lists[0]) : lists[0])
          : null;
        var lid = activeList ? activeList.lid : null;
        var products = activeList ? (activeList.listcontents || []) : [];

        sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: lid, products: products }));

        if (products.length === 0) {
          if (empty) empty.style.display = 'block';
          if (countEl) countEl.textContent = '(0 items)';
        } else {
          if (countEl) countEl.textContent = '(' + products.length + ' items)';
          showSaveNudge(swat, products.length);
          renderProducts(swat, lid, products, grid, countEl, empty);
          setupShareButton(swat, lid, products.length);
          sessionStorage.removeItem(SS_SHARE_URL_KEY);
        }

        setupListSwitcher(swat, lists, lid, grid, countEl, empty);

        if (swat.evtLayer) {
          swat.evtLayer.addEventListener('sw:removedfromwishlist', function() {
            swat.fetchLists({
              callbackFn: function(freshLists) {
                var activeLid = sessionStorage.getItem(SS_PRODUCTS_KEY)
                  ? (JSON.parse(sessionStorage.getItem(SS_PRODUCTS_KEY)) || {}).lid
                  : lid;
                var activeList = freshLists && freshLists.length
                  ? (freshLists.find(function(l) { return l.lid === activeLid; }) || freshLists[0])
                  : null;
                var currentLid = activeList ? activeList.lid : null;
                var updated = activeList ? (activeList.listcontents || []) : [];
                var count = updated.length;

                sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: currentLid, products: updated }));
                sessionStorage.removeItem(SS_SHARE_URL_KEY);
                renderProducts(swat, currentLid, updated, grid, countEl, empty);
                if (countEl) countEl.textContent = '(' + count + ' items)';
                setupShareButton(swat, currentLid, count);
                setupListSwitcher(swat, freshLists || [], currentLid, grid, countEl, empty);
              }
            });
          });
        }
      },
      errorFn: function() {
        if (loading) loading.style.display = 'none';
      }
    });
  }

  function showSaveNudge(swat, count) {
    if (swat.platform.isLoggedIn()) return;
    if (count < SAVE_NUDGE_THRESHOLD) return;
    if (sessionStorage.getItem('swym-save-nudge-dismissed')) return;

    var nudge = document.getElementById('swym-save-nudge');
    if (!nudge) return;

    var countSpan = document.getElementById('swym-save-nudge-count');
    if (countSpan) countSpan.textContent = count;
    nudge.style.display = '';

    var dismiss = document.getElementById('swym-save-nudge-dismiss');
    if (dismiss) {
      dismiss.addEventListener('click', function() {
        nudge.style.display = 'none';
        sessionStorage.setItem('swym-save-nudge-dismissed', 'true');
      });
    }
  }

  // IMPORTANT: Replace the HTML below with your theme's collection card structure
  // to match the look and feel of your collection page
  async function renderProducts(swat, lid, products, container, countEl, emptyEl) {
    container.setAttribute('aria-busy', 'true');
    container.innerHTML = '';

    if (!products || products.length === 0) {
      if (emptyEl) emptyEl.style.display = 'block';
      container.setAttribute('aria-busy', 'false');
      return;
    }

    if (emptyEl) emptyEl.style.display = 'none';

    var shopifyData = await Promise.all(products.map(function(product) {
      var key = String(product.epi);
      if (productJSONCache[key]) return Promise.resolve(productJSONCache[key]);
      return fetchProductJSON(product.du)
        .then(function(data) { productJSONCache[key] = data; return data; })
        .catch(function() { return null; });
    }));

    products.forEach(function(product, index) {
      var shopifyProduct = shopifyData[index];
      var allVariants = shopifyProduct && shopifyProduct.variants ? shopifyProduct.variants : null;
      var savedVariant = allVariants
        ? allVariants.find(function(v) { return String(v.id) === String(product.epi); })
        : null;
      var inStock = savedVariant ? savedVariant.available : product.stk !== 0;
      var displayPrice = savedVariant ? savedVariant.price : product.pr;

      var currentEpi = product.epi;

      var title = esc(shopifyProduct && shopifyProduct.title || product.dt || 'Product');
      var href = esc(shopifyProduct && shopifyProduct.url || product.du || '#');
      var imgSrc = esc(shopifyProduct && (shopifyProduct.featured_image || (shopifyProduct.images && shopifyProduct.images[0])) || product.iu || '');
      var variantSelectId = 'swym-variant-' + esc(String(product.epi));

      var card = document.createElement('div');
      card.className = 'swym-wishlist-card';
      card.innerHTML =
        '<a href="' + href + '">' +
          '<img src="' + imgSrc + '" alt="' + title + '" loading="lazy">' +
        '</a>' +
        '<a href="' + href + '">' + title + '</a>' +
        (displayPrice ? '<span>' + esc(formatPrice(displayPrice)) + '</span>' : '') +
        '<div class="swym-wishlist-card-actions">' +
          '<label class="swym-wishlist-variant-label"' +
            (allVariants && allVariants.length > 1 ? ' for="' + variantSelectId + '"' : '') + '>' +
            (allVariants && allVariants.length > 1 ? 'Variant' : '&nbsp;') +
          '</label>' +
          (allVariants && allVariants.length > 1
            ? '<select id="' + variantSelectId + '" class="swym-wishlist-variant-select"' +
                ' aria-label="Select variant for ' + title + '"></select>'
            : '<span class="swym-wishlist-variant-static">' +
                (savedVariant && savedVariant.title && savedVariant.title !== 'Default Title'
                  ? esc(savedVariant.title) : '') +
              '</span>') +
          '<button type="button" class="swym-wishlist-cart-btn"' + (!inStock ? ' disabled' : '') +
            ' aria-label="' + (inStock ? 'Add ' + title + ' to cart' : title + ' is out of stock') + '">' +
            (inStock ? 'Add to Cart' : 'Out of Stock') +
          '</button>' +
          '<button type="button" class="swym-wishlist-remove-btn" aria-label="Remove ' + title + ' from wishlist">×</button>' +
        '</div>';

      var variantSelect = card.querySelector('.swym-wishlist-variant-select');
      var cartBtn = card.querySelector('.swym-wishlist-cart-btn');
      var removeBtn = card.querySelector('.swym-wishlist-remove-btn');

      if (variantSelect && allVariants && allVariants.length > 1) {
        allVariants.forEach(function(variant) {
          var option = document.createElement('option');
          option.value = variant.id;
          option.textContent = variant.title + (variant.available ? '' : ' (Out of Stock)');
          option.selected = String(variant.id) === String(product.epi);
          option.disabled = !variant.available;
          variantSelect.appendChild(option);
        });

        variantSelect.addEventListener('change', function() {
          currentEpi = Number(variantSelect.value);
          var selected = allVariants.find(function(v) { return String(v.id) === String(currentEpi); });
          var nowInStock = selected && selected.available;
          cartBtn.disabled = !nowInStock;
          cartBtn.textContent = nowInStock ? 'Add to Cart' : 'Out of Stock';
          cartBtn.setAttribute('aria-label', nowInStock
            ? 'Add ' + title + ' to cart'
            : title + ' is out of stock');
        });
      }

      cartBtn.addEventListener('click', function() {
        if (cartBtn.disabled) return;
        cartBtn.textContent = 'Adding...';
        cartBtn.setAttribute('aria-label', 'Adding ' + title + ' to cart');
        cartBtn.disabled = true;
        fetch('/cart/add.js', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ items: [{ id: currentEpi, quantity: 1 }] })
        })
        .then(function(response) {
          if (response.ok) {
            cartBtn.textContent = 'Added!';
            cartBtn.setAttribute('aria-label', title + ' added to cart');
            setTimeout(function() {
              cartBtn.textContent = 'Add to Cart';
              cartBtn.setAttribute('aria-label', 'Add ' + title + ' to cart');
              cartBtn.disabled = false;
            }, 2000);
          } else {
            cartBtn.textContent = 'Error';
            cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
            cartBtn.disabled = false;
          }
        })
        .catch(function() {
          cartBtn.textContent = 'Error';
          cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
          cartBtn.disabled = false;
        });
      });

      removeBtn.addEventListener('click', function() {
        swat.deleteFromList(
          lid,
          { epi: product.epi, empi: product.empi, du: product.du },
          function() {
            card.remove();
            var remaining = container.querySelectorAll('.swym-wishlist-card');
            if (countEl) countEl.textContent = '(' + remaining.length + ' items)';
            if (remaining.length === 0 && emptyEl) emptyEl.style.display = 'block';
          },
          function() {}
        );
      });

      container.appendChild(card);
    });

    container.setAttribute('aria-busy', 'false');
  }

  function bindShareUrlButtons(modal, copyBtn, url, swat, lid) {
    if (copyBtn) {
      copyBtn.disabled = false;
      var freshCopy = copyBtn.cloneNode(true);
      copyBtn.parentNode.replaceChild(freshCopy, copyBtn);
      freshCopy.addEventListener('click', function() { handleCopyLink(url); });
    }
    modal.querySelectorAll('.swym-share-social-btn').forEach(function(socialBtn) {
      var freshSocial = socialBtn.cloneNode(true);
      socialBtn.parentNode.replaceChild(freshSocial, socialBtn);
      freshSocial.addEventListener('click', function() {
        handleSocialShare(swat, lid, freshSocial.dataset.platform, url);
      });
    });
  }

  function setupShareButton(swat, lid, productCount) {
    var btn = document.getElementById('swym-share-btn');
    if (!btn || !lid) return;

    if (productCount === 0) {
      btn.style.display = 'none';
      return;
    }
    btn.style.display = '';

    if (shareButtonHandler) btn.removeEventListener('click', shareButtonHandler);
    shareButtonHandler = function() { openShareModal(swat, lid); };
    btn.addEventListener('click', shareButtonHandler);
  }

  function openShareModal(swat, lid) {
    var modal = document.getElementById('swym-share-modal');
    if (!modal) return;

    var nameInput = document.getElementById('swym-share-name-input');
    var emailInput = document.getElementById('swym-share-email-input');
    var noteInput = document.getElementById('swym-share-note-input');
    var copyBtn = document.getElementById('swym-share-copy-btn');
    if (nameInput) nameInput.value = '';
    if (emailInput) emailInput.value = '';
    if (noteInput) noteInput.value = '';
    if (copyBtn) copyBtn.disabled = true;
    modal.querySelectorAll('[id$="-feedback"]').forEach(function(el) { el.style.display = 'none'; });

    modal.style.display = '';
    document.body.style.overflow = 'hidden';
    if (nameInput) nameInput.focus();

    var cachedShareUrl = sessionStorage.getItem(SS_SHARE_URL_KEY);
    if (cachedShareUrl) {
      bindShareUrlButtons(modal, copyBtn, cachedShareUrl, swat, lid);
    } else {
      swat.markListPublic(lid, function() {
        swat.generateSharedListURL(lid, function(url) {
          sessionStorage.setItem(SS_SHARE_URL_KEY, url);
          bindShareUrlButtons(modal, copyBtn, url, swat, lid);
        });
      }, function() {
        if (copyBtn) copyBtn.disabled = true;
      });
    }

    var emailBtn = document.getElementById('swym-share-email-btn');
    if (emailBtn) {
      var freshEmail = emailBtn.cloneNode(true);
      emailBtn.parentNode.replaceChild(freshEmail, emailBtn);
      freshEmail.addEventListener('click', function() { handleEmailShare(swat, lid); });
    }

    var closeBtn = document.getElementById('swym-share-modal-close');
    var overlay = document.getElementById('swym-share-modal-overlay');
    if (closeBtn) {
      var freshClose = closeBtn.cloneNode(true);
      closeBtn.parentNode.replaceChild(freshClose, closeBtn);
      freshClose.addEventListener('click', closeShareModal);
    }
    if (overlay) {
      var freshOverlay = overlay.cloneNode(true);
      overlay.parentNode.replaceChild(freshOverlay, overlay);
      freshOverlay.addEventListener('click', closeShareModal);
    }
    document.addEventListener('keydown', handleShareModalKeydown);
  }

  function handleShareModalKeydown(e) {
    if (e.key === 'Escape') {
      closeShareModal();
      return;
    }
    if (e.key !== 'Tab') return;

    var modal = document.getElementById('swym-share-modal-content');
    if (!modal) return;
    var focusable = Array.prototype.slice.call(
      modal.querySelectorAll('a[href],button:not([disabled]),input,textarea,select,[tabindex]:not([tabindex="-1"])')
    ).filter(function(el) { return !el.disabled && el.offsetParent !== null; });
    if (!focusable.length) return;

    var first = focusable[0];
    var last = focusable[focusable.length - 1];
    if (e.shiftKey) {
      if (document.activeElement === first) { e.preventDefault(); last.focus(); }
    } else {
      if (document.activeElement === last) { e.preventDefault(); first.focus(); }
    }
  }

  function closeShareModal() {
    var modal = document.getElementById('swym-share-modal');
    if (modal) modal.style.display = 'none';
    document.body.style.overflow = '';
    document.removeEventListener('keydown', handleShareModalKeydown);
    var trigger = document.getElementById('swym-share-btn');
    if (trigger) trigger.focus();
  }

  function handleCopyLink(url) {
    var feedback = document.getElementById('swym-share-copy-feedback');
    var done = function() {
      showModalFeedback(feedback);
      setTimeout(closeShareModal, 800);
    };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(url).then(done).catch(function() {
        fallbackCopy(url, feedback);
        setTimeout(closeShareModal, 800);
      });
    } else {
      fallbackCopy(url, feedback);
      setTimeout(closeShareModal, 800);
    }
  }

  function handleEmailShare(swat, lid) {
    var nameInput = document.getElementById('swym-share-name-input');
    var emailInput = document.getElementById('swym-share-email-input');
    var noteInput = document.getElementById('swym-share-note-input');
    var emailBtn = document.getElementById('swym-share-email-btn');
    var feedback = document.getElementById('swym-share-email-feedback');
    if (!emailInput) return;

    var email = emailInput.value.trim();
    if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      emailInput.focus();
      return;
    }

    if (emailBtn) emailBtn.disabled = true;
    swat.sendListViaEmail({
      toEmailId: email,
      note: noteInput ? noteInput.value.trim() : '',
      fromName: nameInput ? nameInput.value.trim() : '',
      lid: lid
    }, function() {
      if (emailBtn) emailBtn.disabled = false;
      if (nameInput) nameInput.value = '';
      emailInput.value = '';
      if (noteInput) noteInput.value = '';
      showModalFeedback(feedback);
      setTimeout(closeShareModal, 1000);
    }, function() {
      if (emailBtn) emailBtn.disabled = false;
    });
  }

  function handleSocialShare(swat, lid, platform, shareUrl) {
    var platformUrlTemplate = '';
    if (platform === 'facebook') platformUrlTemplate = 'https://www.facebook.com/sharer.php?u=' + encodeURIComponent(shareUrl) + '&t=';
    else if (platform === 'twitter') platformUrlTemplate = 'https://twitter.com/share?text=&url=' + encodeURIComponent(shareUrl);
    swat.shareListSocial(lid, '', platformUrlTemplate, platform, '', function() {});
    if (platformUrlTemplate) window.open(platformUrlTemplate, '_blank', 'noopener,noreferrer');
    closeShareModal();
  }

  function showModalFeedback(el) {
    if (!el) return;
    el.style.display = '';
    setTimeout(function() { el.style.display = 'none'; }, 2500);
  }

  function fallbackCopy(url, feedbackEl) {
    var ta = document.createElement('textarea');
    ta.value = url;
    ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none;';
    document.body.appendChild(ta);
    ta.select();
    try { document.execCommand('copy'); showModalFeedback(feedbackEl); } catch (e) {}
    document.body.removeChild(ta);
  }

  function formatPrice(priceInCents) {
    return '$' + (priceInCents / 100).toFixed(2);
  }

  function esc(str) {
    return String(str)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  function fetchProductJSON(du) {
    var parts = du.split('/products/');
    if (parts.length < 2) return Promise.reject(new Error('invalid product URL'));
    var url = window.location.origin + window.Shopify.routes.root + 'products/' + parts[1].split('?')[0] + '.js';
    return fetch(url)
      .then(function(response) {
        if (!response.ok) throw new Error('product fetch failed');
        return response.json();
      });
  }

  function setWishlistTitle(name) {
    var titleEl = document.getElementById('swym-wishlist-title');
    if (titleEl) titleEl.textContent = name || 'My Wishlist';
  }

  function setupListSwitcher(swat, lists, activeLid, grid, countEl, emptyEl) {
    var wrapper = document.getElementById('swym-list-switcher-wrapper');
    var switcher = document.getElementById('swym-list-switcher');
    if (!wrapper || !switcher) return;

    if (!swat.isCollectionsEnabled() || lists.length < 2) {
      wrapper.style.display = 'none';
      return;
    }
    wrapper.style.display = '';

    var fresh = switcher.cloneNode(false);
    switcher.parentNode.replaceChild(fresh, switcher);

    lists.forEach(function(list) {
      var option = document.createElement('option');
      option.value = list.lid;
      option.textContent = list.lname + ' (' + list.listcontents.length + ' items)';
      if (list.lid === activeLid) option.selected = true;
      fresh.appendChild(option);
    });

    var activeList = lists.find(function(l) { return l.lid === activeLid; });
    if (activeList) setWishlistTitle(activeList.lname);

    fresh.addEventListener('change', function() {
      var selected = lists.find(function(l) { return l.lid === fresh.value; });
      if (!selected) return;
      var products = selected.listcontents || [];
      sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: selected.lid, products: products }));
      sessionStorage.removeItem(SS_SHARE_URL_KEY);
      var count = products.length;
      if (countEl) countEl.textContent = '(' + count + ' items)';
      setWishlistTitle(selected.lname);
      setupShareButton(swat, selected.lid, count);
      renderProducts(swat, selected.lid, products, grid, countEl, emptyEl);
    });
  }

  window.SwymCallbacks = window.SwymCallbacks || [];
  window.SwymCallbacks.push(initWishlistPage);
})();

As with all card styles, pull border colour, border radius, and font values from your theme's CSS variables rather than hardcoding them.

assets/swym-wishlist-page.css

/* ============================================================
   Swym Wishlist Page — Apparel
   ============================================================ */

/* --- List switcher ---------------------------------------- */
#swym-list-switcher-wrapper {
  display: flex;
  align-items: center;
  gap: 6px;
}

#swym-list-switcher-wrapper label {
  font-size: 1rem;
  color: #374151;
  white-space: nowrap;
}

#swym-list-switcher {
  padding: 7px 12px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 1rem;
  background: #fff;
  cursor: pointer;
}

#swym-list-switcher:focus {
  outline: 2px solid #6366f1;
  outline-offset: 2px;
}

/* --- Share button ----------------------------------------- */
#swym-share-btn {
  padding: 9px 18px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background: #fff;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

#swym-share-btn:hover {
  background: #f3f4f6;
  border-color: #9ca3af;
}

/* --- Loading / empty states ------------------------------- */
#swym-wishlist-loading {
  padding: 3rem 0;
  text-align: center;
  color: #6b7280;
  font-size: 1.0625rem;
}

#swym-wishlist-empty {
  padding: 4rem 0;
  text-align: center;
}

#swym-wishlist-empty p {
  margin: 0 0 0.5rem;
  color: #374151;
}

#swym-wishlist-empty p:first-child {
  font-size: 1.25rem;
  font-weight: 600;
}

#swym-wishlist-empty p:last-of-type {
  color: #6b7280;
  font-size: 1rem;
  margin-bottom: 1.5rem;
}

#swym-wishlist-empty a {
  display: inline-block;
  padding: 11px 22px;
  background: #111827;
  color: #fff;
  border-radius: 6px;
  font-size: 1rem;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s;
}

#swym-wishlist-empty a:hover {
  background: #374151;
}

/* --- Nudges ----------------------------------------------- */
#swym-login-nudge {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 18px;
  margin-bottom: 1.5rem;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 8px;
  font-size: 1rem;
  color: #0369a1;
}

#swym-login-nudge p {
  margin: 0;
  flex: 1;
}

#swym-login-nudge a {
  font-weight: 600;
  color: #0369a1;
  text-decoration: underline;
}

#swym-save-nudge {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 18px;
  margin-bottom: 1.5rem;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 8px;
  font-size: 1rem;
  color: #92400e;
}

#swym-save-nudge p {
  margin: 0;
  flex: 1;
  line-height: 1.5;
}

#swym-save-nudge a {
  color: #92400e;
  font-weight: 600;
}

#swym-save-nudge-dismiss {
  flex-shrink: 0;
  padding: 0 4px;
  background: none;
  border: none;
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
  color: #92400e;
  opacity: 0.7;
}

#swym-save-nudge-dismiss:hover {
  opacity: 1;
}

/* --- Product grid ----------------------------------------- */
#swym-wishlist-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  align-items: start;
  gap: 1.5rem;
  margin-top: 2rem;
}

#swym-wishlist-grid[aria-busy="true"] {
  min-height: 600px;
}

@media (max-width: 640px) {
  #swym-wishlist-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
  }
}

/* --- Product card ----------------------------------------- */
.swym-wishlist-card {
  display: flex;
  flex-direction: column;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  overflow: hidden;
  background: #fff;
  transition: box-shadow 0.15s;
}

.swym-wishlist-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.swym-wishlist-card > a:first-child {
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #f9fafb;
}

.swym-wishlist-card > a:first-child img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.2s;
}

.swym-wishlist-card:hover > a:first-child img {
  transform: scale(1.03);
}

.swym-wishlist-card > a:nth-child(2) {
  display: block;
  padding: 14px 14px 4px;
  font-size: 1rem;
  font-weight: 500;
  color: #111827;
  text-decoration: none;
  line-height: 1.4;
}

.swym-wishlist-card > a:nth-child(2):hover {
  text-decoration: underline;
}

.swym-wishlist-card > span {
  padding: 4px 14px 0;
  font-size: 1.0625rem;
  font-weight: 600;
  color: #111827;
}

/* --- Card actions ----------------------------------------- */
.swym-wishlist-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 14px 14px;
  margin-top: auto;
}

/* --- Variant selector ------------------------------------- */
.swym-wishlist-variant-label {
  flex-basis: 100%;
  font-size: 0.875rem;
  font-weight: 500;
  color: #374151;
  margin-bottom: -2px;
}

.swym-wishlist-variant-select {
  flex: 1 0 100%;
  box-sizing: border-box;
  height: 38px;
  padding: 0 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 0.9375rem;
  background: #fff;
  color: #111827;
  cursor: pointer;
  appearance: auto;
}

.swym-wishlist-variant-select:focus {
  outline: 2px solid #6366f1;
  outline-offset: 2px;
}

/* Exact same height as the select so single-variant cards stay aligned */
.swym-wishlist-variant-static {
  flex: 1 0 100%;
  box-sizing: border-box;
  height: 38px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  font-size: 0.9375rem;
  color: #6b7280;
  display: flex;
  align-items: center;
}

/* --- Cart / remove buttons -------------------------------- */
.swym-wishlist-cart-btn {
  flex: 1;
  padding: 9px 12px;
  background: #111827;
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 0.9375rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}

.swym-wishlist-cart-btn:hover:not(:disabled) {
  background: #374151;
}

.swym-wishlist-cart-btn:disabled {
  background: #9ca3af;
  cursor: not-allowed;
}

.swym-wishlist-remove-btn {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  padding: 0;
  background: none;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  color: #6b7280;
  transition: color 0.15s, border-color 0.15s;
}

.swym-wishlist-remove-btn:hover {
  color: #ef4444;
  border-color: #fca5a5;
}

/* --- Share modal ------------------------------------------ */
#swym-share-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}

#swym-share-modal-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: #111827;
}

#swym-share-modal-close {
  padding: 4px 8px;
  background: none;
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  color: #6b7280;
  border-radius: 4px;
  transition: background 0.15s, color 0.15s;
}

#swym-share-modal-close:hover {
  background: #f3f4f6;
  color: #111827;
}

.swym-share-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 1.25rem;
}

.swym-share-section label {
  font-size: 0.9375rem;
  font-weight: 500;
  color: #374151;
}

.swym-share-section input,
.swym-share-section textarea {
  padding: 9px 12px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 1rem;
  color: #111827;
  background: #fff;
  resize: vertical;
}

.swym-share-section input:focus,
.swym-share-section textarea:focus {
  outline: 2px solid #6366f1;
  outline-offset: 2px;
}

.swym-share-section textarea {
  min-height: 72px;
}

#swym-share-email-btn {
  align-self: flex-start;
  padding: 10px 20px;
  background: #111827;
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}

#swym-share-email-btn:hover:not(:disabled) {
  background: #374151;
}

#swym-share-email-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

#swym-share-email-feedback,
#swym-share-copy-feedback {
  font-size: 0.9375rem;
  color: #16a34a;
  font-weight: 500;
}

.swym-share-divider-label {
  margin: 0 0 8px;
  font-size: 0.9375rem;
  color: #9ca3af;
  text-align: center;
  position: relative;
}

.swym-share-divider-label::before,
.swym-share-divider-label::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 38%;
  height: 1px;
  background: #e5e7eb;
}

.swym-share-divider-label::before { left: 0; }
.swym-share-divider-label::after  { right: 0; }

.swym-share-social-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.swym-share-social-btn,
#swym-share-copy-btn {
  flex: 1;
  padding: 9px 14px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background: #fff;
  font-size: 0.9375rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  white-space: nowrap;
}

.swym-share-social-btn:hover,
#swym-share-copy-btn:hover:not(:disabled) {
  background: #f3f4f6;
  border-color: #9ca3af;
}

#swym-share-copy-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.swym-wishlist-collections-v2-container {
  display: none !important;
}

The selectors used across the apparel setup are:

SelectorPurpose
#swym-list-switcher-wrapperWrapper for the list <select> (hidden unless shopper has 2+ lists with Multiple Wishlists enabled)
#swym-list-switcherThe <select> element for switching between lists
.swym-wishlist-cardIndividual product card
.swym-wishlist-card-actionsActions container inside each card
.swym-wishlist-variant-labelLabel above the variant <select>
.swym-wishlist-variant-selectVariant dropdown - present only when product has 2+ variants
.swym-wishlist-cart-btnAdd to Cart button; [disabled] = out of stock or no in-stock variant selected
.swym-wishlist-remove-btnRemove from wishlist button
#swym-share-btnShare Wishlist button in the header row
#swym-share-modalShare modal overlay wrapper
#swym-share-modal-contentShare modal panel
#swym-share-modal-headerShare modal title row
.swym-share-sectionShare modal content section (email form, social row)
.swym-share-social-rowRow of social share buttons
.swym-share-social-btnIndividual social share button
.swym-share-divider-label"or Share via" label between sections
#swym-login-nudgeSign-in prompt for anonymous users
#swym-save-nudge"Don't lose your list" banner for anonymous users with 3+ items