Dynamic SKU on product page

Dynamic SKU on product page

In edit code, add the following liquid in Block.

Idea
{% doc %}
  @prompt
    Create a dynamic SKU display that automatically updates when product variants are changed without requiring a page refresh. The SKU should be visible on the product page and change instantly when customers select different variant options like size, color, or other attributes.

{% enddoc %}
{% assign ai_gen_id = block.id | replace: '_', '' | downcase %}

{% style %}
  .ai-sku-display-{{ ai_gen_id }} {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: {{ block.settings.padding }}px;
    background-color: {{ block.settings.background_color }};
    border: {{ block.settings.border_width }}px solid {{ block.settings.border_color }};
    border-radius: {{ block.settings.border_radius }}px;
    font-family: {{ block.settings.font_family }};
    font-size: {{ block.settings.font_size }}px;
    color: {{ block.settings.text_color }};
    margin: {{ block.settings.margin_top }}px 0 {{ block.settings.margin_bottom }}px 0;
  }

  .ai-sku-display__label-{{ ai_gen_id }} {
    font-weight: {{ block.settings.label_font_weight }};
    color: {{ block.settings.label_color }};
    {% if block.settings.label_uppercase %}
      text-transform: uppercase;
    {% endif %}
  }

  .ai-sku-display__value-{{ ai_gen_id }} {
    font-weight: {{ block.settings.value_font_weight }};
    color: {{ block.settings.value_color }};
    font-family: monospace;
    {% if block.settings.value_uppercase %}
      text-transform: uppercase;
    {% endif %}
  }

  .ai-sku-display__unavailable-{{ ai_gen_id }} {
    color: {{ block.settings.unavailable_color }};
    font-style: italic;
  }

  @media screen and (max-width: 749px) {
    .ai-sku-display-{{ ai_gen_id }} {
      font-size: {{ block.settings.font_size | times: 0.9 }}px;
      padding: {{ block.settings.padding | times: 0.8 }}px;
    }
  }
{% endstyle %}

<dynamic-sku-display-{{ ai_gen_id }}
  class="ai-sku-display-{{ ai_gen_id }}"
  data-product-id="{{ product.id }}"
  {{ block.shopify_attributes }}
>
  <span class="ai-sku-display__label-{{ ai_gen_id }}">
    {{ block.settings.label_text }}
  </span>
  <span 
    class="ai-sku-display__value-{{ ai_gen_id }}"
    data-sku-value
  >
    {% if product.selected_or_first_available_variant.sku != blank %}
      {{ product.selected_or_first_available_variant.sku }}
    {% else %}
      <span class="ai-sku-display__unavailable-{{ ai_gen_id }}">
        {{ block.settings.no_sku_text }}
      </span>
    {% endif %}
  </span>
</dynamic-sku-display-{{ ai_gen_id }}>

<script>
(function() {
  class DynamicSkuDisplay{{ ai_gen_id }} extends HTMLElement {
    constructor() {
      super();
      this.productId = this.dataset.productId;
      this.skuValueElement = this.querySelector('[data-sku-value]');
      this.noSkuText = '{{ block.settings.no_sku_text | escape }}';
      this.unavailableClass = 'ai-sku-display__unavailable-{{ ai_gen_id }}';
    }

    connectedCallback() {
      this.setupVariantChangeListener();
      this.setupUrlChangeListener();
    }

    setupVariantChangeListener() {
      document.addEventListener('variant:change', (event) => {
        if (event.detail && event.detail.variant) {
          this.updateSku(event.detail.variant);
        }
      });

      const variantSelectors = document.querySelectorAll('variant-radios, variant-selects');
      variantSelectors.forEach(selector => {
        selector.addEventListener('change', (event) => {
          const selectedVariant = this.getSelectedVariant();
          if (selectedVariant) {
            this.updateSku(selectedVariant);
          }
        });
      });

      const variantInputs = document.querySelectorAll('input[name="id"], select[name="id"]');
      variantInputs.forEach(input => {
        input.addEventListener('change', () => {
          const selectedVariant = this.getSelectedVariant();
          if (selectedVariant) {
            this.updateSku(selectedVariant);
          }
        });
      });
    }

    setupUrlChangeListener() {
      const observer = new MutationObserver(() => {
        const urlParams = new URLSearchParams(window.location.search);
        const variantId = urlParams.get('variant');
        if (variantId) {
          this.fetchVariantData(variantId);
        }
      });

      observer.observe(document, { subtree: true, childList: true });

      window.addEventListener('popstate', () => {
        const urlParams = new URLSearchParams(window.location.search);
        const variantId = urlParams.get('variant');
        if (variantId) {
          this.fetchVariantData(variantId);
        }
      });
    }

    getSelectedVariant() {
      const variantInput = document.querySelector('input[name="id"]:checked, select[name="id"]');
      if (!variantInput) return null;

      const variantId = variantInput.value;
      return this.getVariantById(variantId);
    }

    getVariantById(variantId) {
      if (!window.productVariants) {
        this.fetchProductData();
        return null;
      }

      return window.productVariants.find(variant => variant.id == variantId);
    }

    async fetchProductData() {
      try {
        const response = await fetch(`/products/${window.location.pathname.split('/').pop()}.js`);
        const product = await response.json();
        window.productVariants = product.variants;
      } catch (error) {
        console.error('Error fetching product data:', error);
      }
    }

    async fetchVariantData(variantId) {
      try {
        const response = await fetch(`/products/${window.location.pathname.split('/').pop()}.js`);
        const product = await response.json();
        const variant = product.variants.find(v => v.id == variantId);
        if (variant) {
          this.updateSku(variant);
        }
      } catch (error) {
        console.error('Error fetching variant data:', error);
      }
    }

    updateSku(variant) {
      if (!this.skuValueElement) return;

      this.skuValueElement.classList.remove(this.unavailableClass);

      if (variant.sku && variant.sku.trim() !== '') {
        this.skuValueElement.textContent = variant.sku;
      } else {
        this.skuValueElement.innerHTML = `<span class="${this.unavailableClass}">${this.noSkuText}</span>`;
      }

      this.dispatchEvent(new CustomEvent('sku:updated', {
        detail: {
          sku: variant.sku,
          variant: variant
        },
        bubbles: true
      }));
    }
  }

  customElements.define('dynamic-sku-display-{{ ai_gen_id }}', DynamicSkuDisplay{{ ai_gen_id }});
})();
</script>

{% schema %}
{
  "name": "Dynamic SKU Display",
  "settings": [
    {
      "type": "header",
      "content": "Content"
    },
    {
      "type": "text",
      "id": "label_text",
      "label": "Label text",
      "default": "SKU:"
    },
    {
      "type": "text",
      "id": "no_sku_text",
      "label": "No SKU text",
      "default": "N/A"
    },
    {
      "type": "header",
      "content": "Styling"
    },
    {
      "type": "select",
      "id": "font_family",
      "label": "Font family",
      "options": [
        {
          "value": "inherit",
          "label": "Inherit"
        },
        {
          "value": "Arial, sans-serif",
          "label": "Arial"
        },
        {
          "value": "Georgia, serif",
          "label": "Georgia"
        },
        {
          "value": "monospace",
          "label": "Monospace"
        }
      ],
      "default": "inherit"
    },
    {
      "type": "range",
      "id": "font_size",
      "min": 10,
      "max": 24,
      "step": 1,
      "unit": "px",
      "label": "Font size",
      "default": 14
    },
    {
      "type": "color",
      "id": "text_color",
      "label": "Text color",
      "default": "#000000"
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background color",
      "default": "#f8f8f8"
    },
    {
      "type": "header",
      "content": "Label Style"
    },
    {
      "type": "color",
      "id": "label_color",
      "label": "Label color",
      "default": "#666666"
    },
    {
      "type": "select",
      "id": "label_font_weight",
      "label": "Label font weight",
      "options": [
        {
          "value": "normal",
          "label": "Normal"
        },
        {
          "value": "bold",
          "label": "Bold"
        },
        {
          "value": "600",
          "label": "Semi-bold"
        }
      ],
      "default": "normal"
    },
    {
      "type": "checkbox",
      "id": "label_uppercase",
      "label": "Uppercase label",
      "default": false
    },
    {
      "type": "header",
      "content": "SKU Value Style"
    },
    {
      "type": "color",
      "id": "value_color",
      "label": "SKU value color",
      "default": "#000000"
    },
    {
      "type": "select",
      "id": "value_font_weight",
      "label": "SKU value font weight",
      "options": [
        {
          "value": "normal",
          "label": "Normal"
        },
        {
          "value": "bold",
          "label": "Bold"
        },
        {
          "value": "600",
          "label": "Semi-bold"
        }
      ],
      "default": "bold"
    },
    {
      "type": "checkbox",
      "id": "value_uppercase",
      "label": "Uppercase SKU value",
      "default": false
    },
    {
      "type": "color",
      "id": "unavailable_color",
      "label": "Unavailable SKU color",
      "default": "#999999"
    },
    {
      "type": "header",
      "content": "Border & Spacing"
    },
    {
      "type": "range",
      "id": "padding",
      "min": 0,
      "max": 30,
      "step": 2,
      "unit": "px",
      "label": "Padding",
      "default": 12
    },
    {
      "type": "range",
      "id": "margin_top",
      "min": 0,
      "max": 50,
      "step": 2,
      "unit": "px",
      "label": "Top margin",
      "default": 10
    },
    {
      "type": "range",
      "id": "margin_bottom",
      "min": 0,
      "max": 50,
      "step": 2,
      "unit": "px",
      "label": "Bottom margin",
      "default": 10
    },
    {
      "type": "range",
      "id": "border_width",
      "min": 0,
      "max": 4,
      "step": 1,
      "unit": "px",
      "label": "Border width",
      "default": 1
    },
    {
      "type": "color",
      "id": "border_color",
      "label": "Border color",
      "default": "#e0e0e0"
    },
    {
      "type": "range",
      "id": "border_radius",
      "min": 0,
      "max": 20,
      "step": 1,
      "unit": "px",
      "label": "Border radius",
      "default": 4
    }
  ],
  "presets": [
    {
      "name": "Dynamic SKU Display"
    }
  ]
}
{% endschema %}

    • Related Articles

    • Enable SKU in predictive search

      In predictive-search.js, add the following: const url = new URL(Theme.routes.predictive_search_url, location.origin); url.searchParams.set('q', searchTerm); url.searchParams.set('resources[limit_scope]', 'each'); ...
    • Enlarge swatch size in product page

      In product page customize, add the following code in custom css: .swatch-input__input + .swatch-input__label { --swatch-input--size: 4rem !important; }
    • Display bundle items in product page

      Add Metafields to Bundle Product Example metafield: Namespace & key: custom.bundle_items Type: List of Products (this is the ideal type for linking individual Shopify products) Assign Metafields to Bundle Product: Go to the bundle product in your ...
    • Add “Description” title before the description on product page

      Add a custom liquid section in product page after add to cart button. Insert the following liquid in the custom liquid. {% if product.description != '' %} <br> <h3 style="color: #EC1C24; font-weight: 600;">DESCRIPTION & SPECIFICATIONS</h3> {% endif ...
    • Hide mega menu links that contain 0 product

      Add the following in the mega-menu-list.liquid {% for childLink in link.links %} {% if link.type == 'collection_link' %} {% assign collection = link.object %} {% if collection.products_count > 0 %} {% assign break_after_modulo = forloop.index | ...