Dynamic product variation price

Topic summary

Custom variant picker needs to show dynamic prices instead of fixed translation strings. The current implementation uses translation keys (e.g., cro-product-page.variants.set_3_price) for each option, which worked for a single fixed-price product but not for international (non‑EU) sales.

Key details:

  • Code snippet includes CSS/HTML for variant tiles with titles, images, and prices sourced from translation strings; JavaScript is partially shown and appears truncated. Images are referenced; the snippet is central to understanding the setup.
  • Request is to replace static translation-based prices with a dynamic tag that reflects the selected variant’s actual price (and likely respects regional pricing rules).

Next steps / actions:

  • A responder requested the store URL and password (if applicable) to review and provide the exact solution code.

Status:

  • No solution provided yet; pending access details from the requester. Unanswered: how to bind the selected variant to live price output (Liquid/JS) and ensure correct international pricing display.
Summarized with AI on December 17. AI used: gpt-5.

Hey

I use this code as a redesigned variant picker, however from now on, instead of a translation string in the price, we would like to dynamically import it. It was a fix price 1 product thing, but now we sell outside of EU as well, so it would be better if we could a dynamic tag.

Could you help me please?

<style>
        .js.product-form__input.product-form__input--pill,
        fieldset.js.product-form__input.product-form__input--pill,
        .product-form__input[data-product-option],
        .product-form__buttons .product-form__input {
            display: none !important;
        }

        variant-selects,
        .variant-selects,
        .product-variant-selects {
            display: none !important;
        }

        .cro-variant-picker {
            width: 100%;
            font-family: 'Arial', sans-serif;
        }

        .cro-variant-option {
            display: flex;
            align-items: center;
            background: linear-gradient(to bottom, #FFFFFF 0%, #DEF0F2 100%);
            border: 1px solid rgba(0, 0, 0, 0.2);
            border-radius: 12px;
            padding: 8px 16px;
            margin-bottom: 8px;
            cursor: pointer;
            transition: all 0.2s ease;
            position: relative;
            transform: translateY(0px);
        }

        .cro-variant-option:hover {
            transform: translateY(-2px);
        }

        .cro-variant-option.selected {
            border: 2px solid #00ffc2 !important;
            transform: translateY(-3px) !important;
            box-shadow: 0 4px 12px rgba(0, 255, 194, 0.3);
        }

        .cro-variant-checkbox {
            width: 18px;
            height: 18px;
            border: 2px solid rgba(0, 0, 0, 0.3);
            border-radius: 4px;
            margin-right: 12px;
            position: relative;
            flex-shrink: 0;
            transition: all 0.2s ease;
            background: #FFFFFF;
        }

        .cro-variant-option.selected .cro-variant-checkbox {
            background: #333333;
            border-color: #333333;
        }

        .cro-variant-checkbox::after {
            content: '✓';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 14px;
            font-weight: bold;
            opacity: 0;
            transition: opacity 0.2s ease;
        }

        .cro-variant-option.selected .cro-variant-checkbox::after {
            opacity: 1;
        }

        .cro-variant-details {
            flex: 1;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .cro-variant-info {
            flex: 1;
        }

        .cro-variant-title {
            font-size: 15px;
            font-weight: 600;
            color: #333;
            margin-bottom: 0px;
        }

        .cro-variant-prices {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .cro-variant-price {
            font-size: 15px;
            font-weight: bold;
            color: #333;
        }

        .cro-variant-original-price {
            font-size: 13px;
            color: #999;
            text-decoration: line-through;
        }

        .cro-variant-image {
            width: 70px;
            height: 70px;
            border-radius: 8px;
            object-fit: contain;
            margin-left: 12px;
            flex-shrink: 0;
        }

        .cro-variant-badge {
            position: absolute;
            top: -6px;
            right: 16px;
            background: #ff4757;
            color: white;
            font-size: 12px;
            padding: 4px 8px;
            border-radius: 12px;
            font-weight: 600;
        }

        @media (max-width: 768px) {
            .cro-variant-picker {
                margin: 0;
            }

            .cro-variant-option {
                padding: 12px 16px;
            }

            .cro-variant-details {
                flex-direction: column;
                align-items: flex-start;
                gap: 8px;
            }

            .cro-variant-image {
                width: 60px;
                height: 60px;
                margin-left: 12px;
            }

            .cro-variant-title {
                font-size: 15px;
            }

            .cro-variant-price {
                font-size: 15px;
            }
        }
    </style>

    <!-- Simulated radio buttons for testing -->
    <div style="display: none;">
        <input type="radio" name="variant" data-option-value-id="5714789400914" checked>
        <input type="radio" name="variant" data-option-value-id="5574711837010">
        <input type="radio" name="variant" data-option-value-id="5574711640402">
        <input type="radio" name="variant" data-option-value-id="5574711673170">
    </div>

    <div class="cro-variant-picker">
        <div class="cro-variant-option selected" data-option-value-id="5714789400914" onclick="selectVariant(this)">
            <div class="cro-variant-checkbox"></div>
            <div class="cro-variant-details">
                <div class="cro-variant-info">
                    <div class="cro-variant-title">{{ 'cro-product-page.variants.set_1_title' | t }}</div>
                    <div class="cro-variant-prices">
                        <span class="cro-variant-price">{{ 'cro-product-page.variants.set_1_price' | t }}</span>
                    </div>
                </div>
                <img src="https://cdn.shopify.com/s/files/1/0934/0328/3794/files/main-prod-pic.webp?v=1753203171" 
                     alt="{{ 'cro-product-page.variants.set_1_alt' | t }}" class="cro-variant-image">
            </div>
        </div>

        <div class="cro-variant-option" data-option-value-id="5574711837010" onclick="selectVariant(this)">
            <div class="cro-variant-checkbox"></div>
            <div class="cro-variant-details">
                <div class="cro-variant-info">
                    <div class="cro-variant-title">{{ 'cro-product-page.variants.set_2_title' | t }}</div>
                    <div class="cro-variant-prices">
                        <span class="cro-variant-price">{{ 'cro-product-page.variants.set_2_price' | t }}</span>
                        <span class="cro-variant-original-price">{{ 'cro-product-page.variants.set_2_price_og' | t }}</span>
                    </div>
                </div>
                <img src="https://cdn.shopify.com/s/files/1/0934/0328/3794/files/cro-2-jelzo.webp?v=1753243447" 
                     alt="{{ 'cro-product-page.variants.set_2_alt' | t }}" class="cro-variant-image">
            </div>
            <div class="cro-variant-badge">{{ 'cro-product-page.variants.popular_badge' | t }}</div>
        </div>

        <div class="cro-variant-option" data-option-value-id="5574711640402" onclick="selectVariant(this)">
            <div class="cro-variant-checkbox"></div>
            <div class="cro-variant-details">
                <div class="cro-variant-info">
                    <div class="cro-variant-title">{{ 'cro-product-page.variants.set_3_title' | t }}</div>
                    <div class="cro-variant-prices">
                        <span class="cro-variant-price">{{ 'cro-product-page.variants.set_3_price' | t }}</span>
                        <span class="cro-variant-original-price">{{ 'cro-product-page.variants.set_3_price_og' | t }}</span>
                    </div>
                </div>
                <img src="https://cdn.shopify.com/s/files/1/0934/0328/3794/files/cro-3-jelzo.webp?v=1753243447" 
                     alt="{{ 'cro-product-page.variants.set_3_alt' | t }}" class="cro-variant-image">
            </div>
        </div>

        <div class="cro-variant-option" data-option-value-id="5574711673170" onclick="selectVariant(this)">
            <div class="cro-variant-checkbox"></div>
            <div class="cro-variant-details">
                <div class="cro-variant-info">
                    <div class="cro-variant-title">{{ 'cro-product-page.variants.set_4_title' | t }}</div>
                    <div class="cro-variant-prices">
                        <span class="cro-variant-price">{{ 'cro-product-page.variants.set_4_price' | t }}</span>
                        <span class="cro-variant-original-price">{{ 'cro-product-page.variants.set_4_price_og' | t }}</span>
                    </div>
                </div>
                <img src="https://cdn.shopify.com/s/files/1/0934/0328/3794/files/cro-4-jelzo.webp?v=1753243446" 
                     alt="{{ 'cro-product-page.variants.set_4_alt' | t }}" class="cro-variant-image">
            </div>
        </div>
    </div>

    <script>
        function selectVariant(element) {
            // Skip if already selected to avoid unnecessary DOM manipulation
            if (element.classList.contains('selected')) {
                return;
            }
            
            // Remove selected class from all options
            document.querySelectorAll('.cro-variant-option').forEach(option => {
                option.classList.remove('selected');
            });
            
            // Use setTimeout to ensure the class removal is processed before adding the new class
            setTimeout(() => {
                element.classList.add('selected');
            }, 10);
            
            const optionValueId = element.getAttribute('data-option-value-id');
            
            // Try multiple selectors to find the radio button
            let radioButton = document.querySelector(`input[data-option-value-id="${optionValueId}"]`);
            
            // If not found, try other common selectors
            if (!radioButton) {
                radioButton = document.querySelector(`input[value="${optionValueId}"]`);
            }
            
            // If still not found, try by name and index
            if (!radioButton) {
                const allRadios = document.querySelectorAll('input[type="radio"][name*="variant"], input[type="radio"][name*="option"]');
                const optionIndex = Array.from(document.querySelectorAll('.cro-variant-option')).indexOf(element);
                if (allRadios[optionIndex]) {
                    radioButton = allRadios[optionIndex];
                }
            }
            
            if (radioButton) {
                // Uncheck all radio buttons first
                document.querySelectorAll('input[type="radio"]').forEach(radio => {
                    radio.checked = false;
                });
                
                // Check the selected radio button
                radioButton.checked = true;
                
                // Trigger change event
                const changeEvent = new Event('change', { bubbles: true });
                radioButton.dispatchEvent(changeEvent);
                
                // Also trigger input event for some systems
                const inputEvent = new Event('input', { bubbles: true });
                radioButton.dispatchEvent(inputEvent);
                
                console.log('Selected radio button for option value ID:', optionValueId);
            } else {
                console.warn('Radio button not found for option value ID:', optionValueId);
                console.log('Available radio buttons:', document.querySelectorAll('input[type="radio"]'));
            }
        }

        document.addEventListener('DOMContentLoaded', function() {
            // Initialize selection
            const checkedRadio = document.querySelector('input[type="radio"]:checked[data-option-value-id]');
            if (checkedRadio) {
                const optionValueId = checkedRadio.getAttribute('data-option-value-id');
                const customOption = document.querySelector(`[data-option-value-id="${optionValueId}"]`);
                if (customOption) {
                    customOption.classList.add('selected');
                }
            } else {
                // If no radio is checked, select the first option
                const firstOption = document.querySelector('.cro-variant-option');
                if (firstOption) {
                    selectVariant(firstOption);
                }
            }

            // Listen for radio button changes
            const radioButtons = document.querySelectorAll('input[type="radio"]');
            radioButtons.forEach(radio => {
                radio.addEventListener('change', function() {
                    if (this.checked) {
                        const optionValueId = this.getAttribute('data-option-value-id') || this.value;
                        let customOption = document.querySelector(`[data-option-value-id="${optionValueId}"]`);
                        
                        // If not found by ID, try by index
                        if (!customOption) {
                            const radioIndex = Array.from(document.querySelectorAll('input[type="radio"]')).indexOf(this);
                            customOption = document.querySelectorAll('.cro-variant-option')[radioIndex];
                        }
                        
                        if (customOption && !customOption.classList.contains('selected')) {
                            document.querySelectorAll('.cro-variant-option').forEach(option => {
                                option.classList.remove('selected');
                            });
                            customOption.classList.add('selected');
                        }
                    }
                });
            });
        });
    </script>
1 Like

Hey @user3968!

Could you please share your store URL and password (if applicable) so that I can take a look and provide you the solution code?

Looking forward to hearing back from you.

Ok so these are just glorified swatches.

You’d need to change only HTML part of the code and i’d start with something like this:

  <!-- Simulated radio buttons for testing -->

  <div class="cro-variant-picker">
    {% for v in product.variants %}

      <label class="cro-variant-option selected" data-option-value-id="{{ v.id }}" onclick="selectVariant(this)">
        <input type="radio" name="variant" class="hidden" data-option-value-id="{{ v.id }}" {% if forloop.first %} checked {% endif %} />
        <div class="cro-variant-checkbox"></div>
        <div class="cro-variant-details">
            <div class="cro-variant-info">
              <div class="cro-variant-title">{{ v.title }}</div>
              <div class="cro-variant-prices">
                  <span class="cro-variant-price">{{ v.price | money_with_currency }}</span>
              </div>
            </div>
            <img src="{{ v.image | img_url: 'master' }}" 
            alt="{{ v.image.alt | default: v.title }}" class="cro-variant-image">
        </div>
        {% if forloop.index == 2 %}
          <div class="cro-variant-badge">{{ 'cro-product-page.variants.popular_badge' | t }}</div>
        {% endif %}  
      </label>
    {% endfor %}
  </div>

Using <label> instead of <div> and plaing <input>s inside the labels we may remove the javascript portion (with some CSS changes).

Then you’d also need to properly set your product titles and assign images to variant (if not done already)

1 Like

Hi @user3968

Try this code.

<style>
  .js.product-form__input,
  variant-selects,
  .variant-selects,
  .product-variant-selects {
    display: none !important;
  }

  .cro-variant-picker {
    width: 100%;
    font-family: Arial, sans-serif;
  }

  .cro-variant-option {
    display: flex;
    align-items: center;
    background: linear-gradient(to bottom, #ffffff 0%, #def0f2 100%);
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 12px;
    padding: 8px 16px;
    margin-bottom: 8px;
    cursor: pointer;
    transition: all .2s ease;
    position: relative;
  }

  .cro-variant-option:hover {
    transform: translateY(-2px);
  }

  .cro-variant-option.selected {
    border: 2px solid #00ffc2;
    box-shadow: 0 4px 12px rgba(0,255,194,.3);
  }

  .cro-variant-checkbox {
    width: 18px;
    height: 18px;
    border: 2px solid rgba(0,0,0,.3);
    border-radius: 4px;
    margin-right: 12px;
    position: relative;
    background: #fff;
  }

  .cro-variant-option.selected .cro-variant-checkbox {
    background: #333;
    border-color: #333;
  }

  .cro-variant-checkbox::after {
    content: "✓";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    font-size: 14px;
    opacity: 0;
  }

  .cro-variant-option.selected .cro-variant-checkbox::after {
    opacity: 1;
  }

  .cro-variant-details {
    flex: 1;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .cro-variant-title {
    font-size: 15px;
    font-weight: 600;
    color: #333;
  }

  .cro-variant-prices {
    display: flex;
    gap: 8px;
    align-items: center;
  }

  .cro-variant-price {
    font-size: 15px;
    font-weight: bold;
  }

  .cro-variant-original-price {
    font-size: 13px;
    color: #999;
    text-decoration: line-through;
  }

  .cro-variant-image {
    width: 70px;
    height: 70px;
    border-radius: 8px;
    object-fit: contain;
    margin-left: 12px;
  }

  .cro-variant-badge {
    position: absolute;
    top: -6px;
    right: 16px;
    background: #ff4757;
    color: #fff;
    font-size: 12px;
    padding: 4px 8px;
    border-radius: 12px;
    font-weight: 600;
  }
</style>
<div class="cro-variant-picker">
  {% for variant in product.variants %}
    <div
      class="cro-variant-option {% if variant.id == product.selected_or_first_available_variant.id %}selected{% endif %}"
      data-variant-id="{{ variant.id }}"
      onclick="selectVariant(this)"
    >
      <div class="cro-variant-checkbox"></div>

      <div class="cro-variant-details">
        <div>
          <div class="cro-variant-title">
            {{ variant.title }}
          </div>

          <div class="cro-variant-prices">
            <span class="cro-variant-price">
              {{ variant.price | money }}
            </span>

            {% if variant.compare_at_price > variant.price %}
              <span class="cro-variant-original-price">
                {{ variant.compare_at_price | money }}
              </span>
            {% endif %}
          </div>
        </div>

        {% if variant.image %}
          <img
            src="{{ variant.image | image_url: width: 140 }}"
            alt="{{ variant.title }}"
            class="cro-variant-image"
          >
        {% endif %}
      </div>

      {% if forloop.index == 2 %}
        <div class="cro-variant-badge">
          {{ 'cro-product-page.variants.popular_badge' | t }}
        </div>
      {% endif %}
    </div>
  {% endfor %}
</div>

<script>
  function selectVariant(element) {
    if (element.classList.contains('selected')) return;

    document.querySelectorAll('.cro-variant-option')
      .forEach(el => el.classList.remove('selected'));

    element.classList.add('selected');

    const variantId = element.dataset.variantId;

    const radio = document.querySelector(
      `input[type="radio"][value="${variantId}"]`
    );

    if (radio) {
      radio.checked = true;
      radio.dispatchEvent(new Event('change', { bubbles: true }));
      radio.dispatchEvent(new Event('input', { bubbles: true }));
    }
  }

  document.addEventListener('DOMContentLoaded', () => {
    const checkedRadio = document.querySelector(
      'input[type="radio"]:checked'
    );

    if (checkedRadio) {
      const variantId = checkedRadio.value;
      const option = document.querySelector(
        `.cro-variant-option[data-variant-id="${variantId}"]`
      );
      if (option) option.classList.add('selected');
    }
  });
</script>

Best regards,
Devcoder :laptop:

1 Like

Thank you very much!

Unfortunately it does not work. On the looks its perfect, but in functionality, it does not change the variant.

1 Like

Hi @user3968

If possible, could you please share the file where you added this code, or send me a Collaborator code? I can check it and let you know what the issue is.

Best regards,
Devcoder :laptop: