Plan Picker files to a new theme (widget alternative)

Overview

This guide explains how to transfer the Yari Flow Plan Picker from an existing Shopify theme to a new theme.

You do not need to follow these steps when the new theme is a duplicate of a theme that already has Yari Flow installed, because the required files and code should already be included.

Find Yari Flow related theme code

If you are a developer, the quickest way to locate Yari Flow related code is to search the entire theme codebase for yariflow In most code editors, you can search the full project using Ctrl + Shift + F or Cmd + Shift + F.

You should also look for filenames containing yariflow.

This will help identify:

  • Yari Flow snippets
  • JavaScript and CSS assets
  • Product form code
  • Variant change event handling
  • Shopify section blocks and schema settings

The transfer process depends on which version of the Yari Flow Plan Picker your current theme uses.

Identify your Plan Picker version

Use the following file structure to determine which implementation is installed.

Liquid Component

You are using the Liquid Component if your theme contains these three files:

snippets/yariflow-plan-picker.liquid
snippets/yariflow-plan-picker-plan-label.liquid
snippets/yariflow-plan-picker-allocations.liquid
assets/yariflow-plan-picker.css
assets/yariflow-plan-picker.js

Plan Picker code files

yariflow-plan-picker.liquid
{% comment %} Yari Flow Plan Picker — Liquid Component. Renders one-time purchase and subscription options; syncs with the product form via a hidden input name="selling_plan". Place inside a product form that has an input with name="id" (variant id): {% render 'yariflow-plan-picker', product: product %} Every setting below can be overridden per render, e.g.: {% render 'yariflow-plan-picker', product: product, layout: 'horizontal', selector_type: 'button', show_savings_badge: true %} Or edit the defaults in the CONFIG block directly if the same values apply store-wide. {% endcomment %} {% comment %} ---------- CONFIG ---------- {% endcomment %} {% assign layout = layout | default: 'vertical' %} {% comment %} vertical | horizontal | hidden {% endcomment %} {% assign selector_type = selector_type | default: 'dropdown' %} {% comment %} dropdown | button {% endcomment %} {% assign onetime_title = onetime_title | default: 'One-time purchase' %} {% assign subscription_title = subscription_title | default: 'Subscribe & Save' %} {% assign frequency_label = frequency_label | default: 'Deliver every' %} {% assign legend_text = legend_text | default: 'Purchase option' %} {% assign show_legend = show_legend | default: false, allow_false: true %} {% assign onetime_first = onetime_first | default: true, allow_false: true %} {% assign show_radio_selector = show_radio_selector | default: true, allow_false: true %} {% assign show_only_first_plan = show_only_first_plan | default: false, allow_false: true %} {% assign show_compare_at_price = show_compare_at_price | default: true, allow_false: true %} {% assign show_savings_badge = show_savings_badge | default: false, allow_false: true %} {% comment %} Inherit the theme's own colors/radius via CSS vars instead of the custom palette below. {% endcomment %} {% assign use_theme_styles = use_theme_styles | default: false, allow_false: true %} {% assign override_theme_styles = override_theme_styles | default: false, allow_false: true %} {% assign max_width = max_width | default: 480 %} {% assign group_gap = group_gap | default: 12 %} {% assign padding_vertical = padding_vertical | default: 14 %} {% assign padding_horizontal = padding_horizontal | default: 16 %} {% assign group_min_height = group_min_height | default: 0 %} {% assign border_radius = border_radius | default: 8 %} {% assign border_width = border_width | default: 2 %} {% assign title_size = title_size | default: 14 %} {% assign price_size = price_size | default: 15 %} {% assign font_weight_title = font_weight_title | default: '500' %} {% assign plan_pill_min_height = plan_pill_min_height | default: 42 %} {% assign plan_pill_radius = plan_pill_radius | default: 6 %} {% assign horizontal_option_width_percent = horizontal_option_width_percent | default: 0 %} {% comment %} 0 = auto width; set 35-100 to fix the horizontal option width {% endcomment %} {% assign horizontal_option_min_width = horizontal_option_min_width | default: 220 %} {% assign border_color = border_color | default: '#e5e5e5' %} {% assign border_selected_color = border_selected_color | default: '#0066cc' %} {% assign bg_color = bg_color | default: '#ffffff' %} {% assign bg_selected_color = bg_selected_color | default: '#f0f7ff' %} {% assign text_color = text_color | default: '#1a1a1a' %} {% assign accent_color = accent_color | default: '#0066cc' %} {% assign savings_badge_bg_color = savings_badge_bg_color | default: '#0066cc' %} {% assign savings_badge_text_color = savings_badge_text_color | default: '#ffffff' %} {% comment %} ---------- END CONFIG ---------- {% endcomment %} {% assign variant = product.selected_or_first_available_variant %} {% assign yari_app_id = 'yari-flow' %} {% comment %} ponytail: one picker per product per page. Pass uid: 'something' if you render two. {% endcomment %} {% assign uid = uid | default: product.id %} {% assign has_subscription = false %} {% for group in product.selling_plan_groups %} {% if group.app_id == yari_app_id %} {% assign has_subscription = true %} {% break %} {% endif %} {% endfor %} {% if has_subscription %} {% capture variant_allocations_json %}{% render "yariflow-plan-picker-allocations", product: product %}{% endcapture %} {% capture variant_data_json %}{ {% for v in product.variants %}{% unless forloop.first %},{% endunless %}"{{ v.id }}":{"price":{{ v.price }},"compare_at_price":{{ v.compare_at_price | default: 0 }},"available":{{ v.available | json }}}{% endfor %} }{% endcapture %} {% assign currency_code = cart.currency.iso_code | default: shop.currency %}
{% if show_legend %} {{ legend_text }} {% else %} {{ legend_text }} {% endif %} {% unless product.requires_selling_plan %}
{% endunless %} {% comment %} Find the selected/default allocation across ALL yari-flow groups {% endcomment %} {% assign selected_alloc = nil %} {% if variant.selected_selling_plan_allocation %} {% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% if variant.selected_selling_plan_allocation.selling_plan.id != plan.id %}{% continue %}{% endif %} {% for alloc in variant.selling_plan_allocations %} {% if alloc.selling_plan.id == plan.id %} {% assign selected_alloc = alloc %} {% break %} {% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% endif %} {% if selected_alloc == nil %} {% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% for alloc in variant.selling_plan_allocations %} {% if alloc.selling_plan.id == plan.id %} {% assign selected_alloc = alloc %} {% break %} {% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% endif %} {% if selected_alloc == nil %} {% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% for v in product.variants %} {% for alloc in v.selling_plan_allocations %} {% if alloc.selling_plan.id == plan.id %} {% assign selected_alloc = alloc %} {% break %} {% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% if selected_alloc %}{% break %}{% endif %} {% endfor %} {% endif %} {% comment %} Count available plans and find first available plan across ALL yari-flow groups {% endcomment %} {% assign available_plans_count = 0 %} {% assign first_available_plan = nil %} {% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% for alloc in variant.selling_plan_allocations %} {% if alloc.selling_plan.id == plan.id %} {% assign available_plans_count = available_plans_count | plus: 1 %} {% if first_available_plan == nil %} {% assign first_available_plan = plan %} {% endif %} {% break %} {% endif %} {% endfor %} {% endfor %} {% endfor %}
{% comment %} Single plan: show name only. Multiple plans: show selector (dropdown or buttons). Both in DOM so JS can toggle on variant change. {% endcomment %} {% if first_available_plan %}{{ first_available_plan.name }}{% endif %}
{{ frequency_label }} {% if selector_type == 'dropdown' %} {% else %}
{% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% assign plan_available = false %} {% assign plan_price = 0 %} {% assign plan_discount_type = '' %} {% for alloc in variant.selling_plan_allocations %} {% if alloc.selling_plan.id == plan.id %} {% assign plan_available = true %} {% assign plan_price = alloc.price %} {% for adj in alloc.selling_plan.price_adjustments %} {% assign plan_discount_type = adj.value_type %} {% break %} {% endfor %} {% break %} {% endif %} {% endfor %} {% unless plan_available %}{% continue %}{% endunless %} {% capture plan_label %}{% render "yariflow-plan-picker-plan-label", plan_name: plan.name, plan_price: plan_price, regular_price: variant.price, discount_type: plan_discount_type %}{% endcapture %} {% endfor %} {% endfor %}
{% endif %}
{{ 'yariflow-plan-picker.css' | asset_url | stylesheet_tag }} {% endif %}
yariflow-plan-picker-plan-label.liquid
{%- comment -%} Plan name with savings suffix: "Every 2 months (save 20%)" for percentage discounts, "Every 2 months (save £4.60)" for flat/fixed ones. Params: plan_name, plan_price (allocation price), regular_price (variant price), discount_type. {%- endcomment -%} {%- assign savings_amount = 0 -%} {%- if regular_price > 0 and plan_price > 0 and plan_price < regular_price -%} {%- assign savings_amount = regular_price | minus: plan_price -%} {%- endif -%} {{- plan_name -}} {%- if savings_amount > 0 -%} {%- if discount_type == 'percentage' -%} {%- assign regular_price_float = regular_price | times: 1.0 -%} {%- assign savings_pct = savings_amount | times: 100.0 | divided_by: regular_price_float | round -%} {%- if savings_pct > 0 %} (save {{ savings_pct }}%){% endif -%} {%- else %} (save {{ savings_amount | money }}){% endif -%} {%- endif -%}
yariflow-plan-picker-allocations.liquid
yariflow-plan-picker-allocations.liquid {% assign yari_group_ids = '|' %} {% for group in product.selling_plan_groups %} {% if group.app_id == 'yari-flow' %} {% assign yari_group_ids = yari_group_ids | append: group.id | append: '|' %} {% endif %} {% endfor %} { {% assign yari_app_id = 'yari-flow' %} {% for variant in product.variants %} "{{ variant.id }}": [ {% assign allocation_entries = '' %} {% for group in product.selling_plan_groups %} {% if group.app_id != yari_app_id %}{% continue %}{% endif %} {% for plan in group.selling_plans %} {% for alloc in variant.selling_plan_allocations %} {% if alloc.selling_plan.id != plan.id %}{% continue %}{% endif %} {% assign disc_type = '' %} {% assign disc_value = 0 %} {% for adj in plan.price_adjustments %} {% assign disc_type = adj.value_type %} {% assign disc_value = adj.value %} {% break %} {% endfor %} {% capture allocation_entry %} { "id": {{ plan.id }}, "group_id": {{ group.id | json }}, "price": {{ alloc.price }}, "compare_at_price": {{ alloc.compare_at_price | default: 0 }}, "name": {{ plan.name | json }}, "discount_type": {{ disc_type | json }}, "discount_value": {{ disc_value | default: 0 }}, {% assign alloc_savings = variant.price | minus: alloc.price %} "savings_amount_formatted": {{ alloc_savings | money | json }} } {% endcapture %} {% if allocation_entries != blank %} {% assign allocation_entries = allocation_entries | append: ',' %} {% endif %} {% assign allocation_entries = allocation_entries | append: allocation_entry %} {% break %} {% endfor %} {% endfor %} {% endfor %} {{ allocation_entries }} ]{% unless forloop.last %},{% endunless %} {% endfor %} }
yariflow-plan-picker.css
.subscription-picker__single-plan-name[aria-hidden="true"], .subscription-picker__plan-selector[aria-hidden="true"] { display: none; } .subscription-picker { width: 100%; max-width: var(--subscription-picker-max-width, 480px); font-size: 14px; color: var(--subscription-picker-text-color, #1a1a1a); margin-bottom: 1.5rem; font-family: inherit; } [data-block-handle="subscription_picker"] { width: 100%; } .subscription-picker[data-layout="hidden"] { display: none; } .subscription-picker__fieldset { display: flex; gap: var(--subscription-picker-group-gap, 0.75rem); padding: 0; border: 0; width: 100%; flex-direction: column; } .subscription-picker[data-layout="horizontal"] .subscription-picker__fieldset { flex-direction: row; flex-wrap: wrap; } .subscription-picker__sr-only { position: absolute; overflow: hidden; clip: rect(0, 0, 0, 0); width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; } .subscription-picker__legend { margin-bottom: 0.75rem; font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--subscription-picker-text-color, #1a1a1a); opacity: 0.7; } .subscription-picker__group { display: block; position: relative; box-sizing: border-box; transition: all 0.2s ease; border: var(--subscription-picker-border-width, 2px) solid var(--subscription-picker-border-color, #e5e5e5); border-radius: var(--subscription-picker-border-radius, 8px); background-color: var(--subscription-picker-bg-color, #fff); color: var(--subscription-picker-text-color, #1a1a1a); overflow: hidden; min-height: var(--subscription-picker-group-min-height, 0); cursor: pointer; } .subscription-picker__group:hover { border-color: color-mix( in srgb, var(--subscription-picker-border-color, #e5e5e5) 70%, black ); } .subscription-picker[data-variant-unavailable="true"] .subscription-picker__group { pointer-events: none; } .subscription-picker[data-variant-unavailable="true"] .subscription-picker__group { cursor: not-allowed; opacity: 0.65; } .subscription-picker[data-variant-unavailable="true"] .subscription-picker__label { cursor: not-allowed; } .subscription-picker__group--selected { border-color: var(--subscription-picker-border-selected-color, #0066cc); background-color: var(--subscription-picker-bg-selected-color, #f0f7ff); box-shadow: 0 0 0 3px color-mix( in srgb, var(--subscription-picker-border-selected-color, #0066cc) 10%, transparent ); } .subscription-picker__group--last { order: 1; } .subscription-picker[data-layout="horizontal"] .subscription-picker__group { width: var( --subscription-picker-horizontal-option-width, calc((100% - var(--subscription-picker-group-gap, 0.75rem)) / 2) ); min-width: var(--subscription-picker-horizontal-option-min-width, 0); } .subscription-picker__input { position: absolute; opacity: 0; width: 0; height: 0; } .subscription-picker__input:focus-visible + .subscription-picker__label { outline: 2px solid var(--subscription-picker-border-selected-color, #0066cc); outline-offset: 2px; } .subscription-picker__label { display: flex; flex-direction: column; padding: var(--subscription-picker-padding-v, 14px) var(--subscription-picker-padding-h, 16px); cursor: pointer; text-align: var(--subscription-picker-text-align, left); } .subscription-picker__topline { display: flex; align-items: center; width: 100%; gap: 10px; } .subscription-picker[data-layout="horizontal"] .subscription-picker__topline { flex-direction: column; align-items: center; text-align: center; } .subscription-picker__radio { flex-shrink: 0; align-self: center; width: 20px; height: 20px; border: 2px solid currentColor; border-radius: 50%; transition: border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1); position: relative; } .subscription-picker__radio::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 8px; height: 8px; background: currentColor; border-radius: 50%; opacity: 0; transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1); } .subscription-picker__label:hover .subscription-picker__radio::after { opacity: 0.35; } .subscription-picker__group--selected .subscription-picker__label .subscription-picker__radio::after { opacity: 1; } .subscription-picker__title { flex: 1; max-width: 70%; line-height: 1.4; font-size: var(--subscription-picker-title-size, 14px); font-weight: var(--subscription-picker-title-weight, 500); } .subscription-picker[data-layout="horizontal"] .subscription-picker__title { max-width: 100%; } .subscription-picker__price { display: flex; justify-content: flex-end; flex-wrap: wrap; align-items: flex-end; flex-direction: column; text-align: right; gap: 2px; line-height: 1.2; margin-left: auto; font-weight: 600; font-size: var(--subscription-picker-price-size, 15px); text-align: right; } .subscription-picker__price s { font-size: 13px; font-weight: 400; opacity: 0.6; text-decoration: line-through; text-align: right; } .subscription-picker[data-layout="horizontal"] .subscription-picker__price { align-self: flex-end; align-items: flex-end; text-align: right; margin-right: 0; } .subscription-picker__content { display: flex; flex-direction: column; opacity: 1; max-height: 1000px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; padding: 0 var(--subscription-picker-padding-h, 16px) var(--subscription-picker-padding-v, 14px); } .subscription-picker__content--indent { margin-left: 0; padding-left: 18px; } .subscription-picker:not(.subscription-picker--content-visible) .subscription-picker__group:not(.subscription-picker__group--selected) .subscription-picker__content { pointer-events: none; opacity: 0; max-height: 0; padding: 0; visibility: hidden; } .subscription-picker__frequency-label { display: block; color: var(--subscription-picker-text-color, #1a1a1a); opacity: 0.75; font-weight: 500; font-size: 12px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.3px; } .subscription-picker__single-plan-name { color: var(--subscription-picker-text-color, #1a1a1a); opacity: 0.85; font-size: 14px; font-weight: 500; padding: 4px 0; } .subscription-picker__select { appearance: none; -webkit-appearance: none; -moz-appearance: none; display: flex; align-items: center; width: 100%; min-height: 44px; padding: 10px 36px 10px 14px; background-color: var(--subscription-picker-bg-color, #fff); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23666' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7' /%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 18px; border: 2px solid var(--subscription-picker-border-color, #e5e5e5); border-radius: 6px; font-size: 14px; font-weight: 500; color: var(--subscription-picker-text-color, #1a1a1a); cursor: pointer; transition: all 0.2s ease; } .subscription-picker__select:hover { border-color: color-mix( in srgb, var(--subscription-picker-border-color, #e5e5e5) 70%, black ); } .subscription-picker__select:focus { outline: none; border-color: var(--subscription-picker-border-selected-color, #0066cc); box-shadow: 0 0 0 3px color-mix( in srgb, var(--subscription-picker-border-selected-color, #0066cc) 10%, transparent ); } .subscription-picker__select:disabled { background-color: color-mix( in srgb, var(--subscription-picker-bg-color, #fff) 95%, black ); cursor: default; opacity: 0.7; } .subscription-picker__buttons { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 8px; padding: 0; font-size: 13px; } .subscription-picker__plan-input { position: absolute; opacity: 0; width: 0; height: 0; } .subscription-picker__plan-label { display: flex; align-items: center; justify-content: center; cursor: pointer; border: 2px solid var(--subscription-picker-border-color, #e5e5e5); text-align: center; min-height: var(--subscription-picker-plan-pill-min-height, 42px); padding: 10px 12px; border-radius: var(--subscription-picker-plan-pill-radius, 6px); font-weight: 500; transition: all 0.2s ease; } .subscription-picker__plan-label:hover { border-color: color-mix( in srgb, var(--subscription-picker-border-color, #e5e5e5) 70%, black ); } .subscription-picker__plan-input:checked + .subscription-picker__plan-label { border-color: var(--subscription-picker-border-selected-color, #0066cc); background-color: var(--subscription-picker-bg-selected-color, #f0f7ff); color: var(--subscription-picker-border-selected-color, #0066cc); } .subscription-picker__plan-input:focus-visible + .subscription-picker__plan-label { outline: 2px solid var(--subscription-picker-border-selected-color, #0066cc); outline-offset: 2px; } .subscription-picker__badge { display: inline-flex; align-items: center; padding: 2px 8px; margin-left: 6px; border-radius: 100px; font-size: 11px; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase; background-color: var(--subscription-picker-badge-bg, #0066cc); color: var(--subscription-picker-badge-color, #ffffff); white-space: nowrap; vertical-align: middle; } /* In custom mode, enforce picker palette even if theme form styles are aggressive. */ .subscription-picker--custom .subscription-picker__group { background-color: var(--subscription-picker-bg-color, #fff) !important; border-color: var(--subscription-picker-border-color, #e5e5e5) !important; color: var(--subscription-picker-text-color, #1a1a1a) !important; } .subscription-picker--custom .subscription-picker__group--selected { background-color: var( --subscription-picker-bg-selected-color, #f0f7ff ) !important; border-color: var( --subscription-picker-border-selected-color, #0066cc ) !important; } .subscription-picker--custom .subscription-picker__select, .subscription-picker--custom .subscription-picker__plan-label { background-color: var(--subscription-picker-bg-color, #fff) !important; border-color: var(--subscription-picker-border-color, #e5e5e5) !important; color: var(--subscription-picker-text-color, #1a1a1a) !important; } .subscription-picker--custom .subscription-picker__plan-input:checked + .subscription-picker__plan-label { background-color: var( --subscription-picker-bg-selected-color, #f0f7ff ) !important; border-color: var( --subscription-picker-border-selected-color, #0066cc ) !important; color: var(--subscription-picker-border-selected-color, #0066cc) !important; }
yariflow-plan-picker.js
/** * Subscription picker: syncs group/plan selection with hidden input [name="selling_plan"], * updates prices when variant or plan changes using variant-allocations data. */ (function () { const PICKER_SELECTOR = "[data-subscription-picker]"; const VARIANT_INPUT_SELECTOR = 'input[name="id"], select[name="id"]'; const PRICE_SELECTOR = [ "[data-product-price]", "[data-price]", "[data-variant-price]", "[data-sale-price]", "[data-current-price]", "[data-regular-price]", '[itemprop="price"]', '[class*="price"]', '[id*="price"]', ".money", ].join(","); const COMPARE_SELECTOR = [ "[data-compare-price]", "[data-compare-at-price]", "[data-was-price]", "[data-original-price]", '[class*="compare"]', '[class*="was-price"]', '[class*="original-price"]', "[data-product-price] s", "[data-product-price] del", '[class*="price"] s', '[class*="price"] del', ].join(","); function formatCurrency(cents, picker) { if (cents == null || isNaN(cents)) return ""; const currency = picker.dataset.currencyCode || "GBP"; return new Intl.NumberFormat(navigator.language, { style: "currency", currency: currency, currencyDisplay: "narrowSymbol", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(Number(cents) / 100); } function planLabel(allocation, regularPrice, picker) { const price = Number(allocation?.price); const regular = Number(regularPrice); if (!(regular > 0) || !(price > 0) || price >= regular) return allocation.name; if (allocation.discount_type === "percentage") { const pct = Math.round(((regular - price) * 100) / regular); return pct > 0 ? `${allocation.name} (save ${pct}%)` : allocation.name; } const savings = allocation.savings_amount_formatted || formatCurrency(regular - price, picker); return savings ? `${allocation.name} (save ${savings})` : allocation.name; } // A native select shows the selected option's own text when closed, so the // suffix only goes in while the list is open — the group badge covers the rest. function toggleOptionSavings(select, show) { Array.from(select.options).forEach(function (option) { const withSavings = option.dataset.savingsLabel; if (!withSavings) return; if (show) { // mousedown and focus both fire on the same click: never re-capture a suffixed label if (option.textContent.trim() === withSavings) return; option.dataset.plainLabel = option.textContent.trim(); option.textContent = withSavings; } else if (option.dataset.plainLabel) { option.textContent = option.dataset.plainLabel; } }); } function parseJSON(str) { try { return JSON.parse( (str || "").trim().replace(//g, "") || "{}", ); } catch { return {}; } } function normalizeVariantId(variantId) { if (variantId == null || variantId === "") return null; const str = String(variantId).trim(); return str.includes("ProductVariant/") ? str.split("/").pop() : str; } function isUnifiedMode(picker) { return !!picker.querySelector('[data-subscription-group][data-group-id="subscription"]'); } function getVariantAllocations(picker, allocationsMap, variantId) { const key = normalizeVariantId(variantId); if (!key) return []; const allAllocations = allocationsMap[key] || allocationsMap[Number(key)] || []; if (isUnifiedMode(picker)) return allAllocations; const validGroupIds = Array.from(picker.querySelectorAll('[data-subscription-group]')) .map(group => String(group.dataset.groupId)) .filter(id => id !== 'onetime'); return allAllocations.filter(alloc => validGroupIds.includes(String(alloc.group_id))); } function getVariantData(variantDataMap, variantId) { const key = normalizeVariantId(variantId); if (!key) return null; return variantDataMap[key] || variantDataMap[Number(key)] || null; } function isVariantAvailable(picker, variantId) { const variantData = getVariantData( parseJSON(picker.dataset.variantData), variantId, ); if (!variantData || variantData.available == null) return true; return variantData.available === true || variantData.available === "true"; } function getVariantInput(picker) { const input = picker.closest("form")?.querySelector(VARIANT_INPUT_SELECTOR); if (input) return input; const scope = picker.closest("section") || picker.closest("main") || document.body; return scope.querySelector(VARIANT_INPUT_SELECTOR); } function isTransparentColor(value) { if (!value) return true; const normalized = String(value).trim().toLowerCase(); return ( normalized === "transparent" || normalized === "rgba(0, 0, 0, 0)" || normalized === "rgba(0,0,0,0)" ); } function firstVisibleColor() { for (let i = 0; i < arguments.length; i += 1) { const color = arguments[i]; if (!isTransparentColor(color)) return color; } return null; } function applyThemeControlStyles(picker) { if (picker.dataset.useThemeStyles !== "true") return; if (picker.dataset.overrideThemeStyles === "true") return; const scope = picker.closest("section") || picker.closest("main") || document.body; const parentForm = picker.closest("form") || scope.querySelector('form[action*="cart/add"]') || scope.querySelector("form"); const submitButton = parentForm?.querySelector( 'button[type="submit"], input[type="submit"]', ) || scope.querySelector('button[type="submit"], input[type="submit"]'); const baseInput = parentForm?.querySelector( 'select, input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]), textarea, .field__input', ) || scope.querySelector( '.field__input, select, input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]), textarea', ); if (!submitButton && !baseInput) return; const buttonStyles = submitButton ? window.getComputedStyle(submitButton) : null; const inputStyles = baseInput ? window.getComputedStyle(baseInput) : null; const borderRadius = buttonStyles?.borderRadius || inputStyles?.borderRadius; const borderWidth = inputStyles?.borderTopWidth || buttonStyles?.borderTopWidth; // Keep colors fully controlled by Liquid theme tokens or custom settings. // Runtime sync should only adjust shape/weight to feel native. if (borderRadius) picker.style.setProperty( "--subscription-picker-border-radius", borderRadius, ); if (borderWidth && borderWidth !== "0px") picker.style.setProperty( "--subscription-picker-border-width", borderWidth, ); } function isPriceBadge(element) { const className = (element.className && String(element.className)) || ""; return /badge/.test(className); } function updatePrices(regularPrice, compareAtPrice) { document.querySelectorAll(PRICE_SELECTOR).forEach(function (element) { if ( element.closest(PICKER_SELECTOR) || element.closest("s") || element.closest("del") ) return; if (element.querySelector(PRICE_SELECTOR + "," + COMPARE_SELECTOR)) return; if (isPriceBadge(element)) return; element.textContent = regularPrice; }); document.querySelectorAll(COMPARE_SELECTOR).forEach(function (element) { if (element.closest(PICKER_SELECTOR)) return; element.textContent = compareAtPrice || ""; }); } function refreshMainPrice(picker, variantId, sellingPlanId) { const variantData = getVariantData( parseJSON(picker.dataset.variantData), variantId, ); if (sellingPlanId) { const allocation = getVariantAllocations( picker, parseJSON(picker.dataset.variantAllocations), variantId, ).find((allocation) => String(allocation.id) === String(sellingPlanId)); if (!allocation) return; const showCompare = picker.dataset.showCompareAtPrice === "true"; const compareAt = showCompare && variantData && variantData.compare_at_price > allocation.price ? formatCurrency(variantData.compare_at_price, picker) : null; updatePrices(formatCurrency(allocation.price, picker), compareAt); } else { if (!variantData) return; updatePrices( formatCurrency(variantData.price, picker), variantData.compare_at_price > variantData.price ? formatCurrency(variantData.compare_at_price, picker) : null, ); } } function updateOneTimePrice(picker, variantId) { const priceElement = picker.querySelector("[data-price-one-time]"); if (!priceElement) return; const variantData = getVariantData( parseJSON(picker.dataset.variantData), variantId, ); if (!variantData) return; const comparePart = picker.dataset.showCompareAtPrice === "true" && variantData.compare_at_price > variantData.price ? `` : ""; priceElement.innerHTML = `${formatCurrency(variantData.price, picker)}` + comparePart; } function updateSubscriptionPriceDisplay( picker, groupElement, variantId, allocation, ) { const priceContainer = groupElement.querySelector( "[data-price-subscription]", ); if (!priceContainer || !allocation) return; const variantData = getVariantData( parseJSON(picker.dataset.variantData), variantId, ); if (!variantData) return; const showCompare = picker.dataset.showCompareAtPrice === "true"; const comparePart = showCompare && variantData.compare_at_price > allocation.price ? `` : ""; priceContainer.innerHTML = `${formatCurrency(allocation.price, picker)}` + comparePart; updateSavingsBadge(picker, groupElement, variantData, allocation); } function updateSavingsBadge(picker, groupElement, variantData, allocation) { if (picker.dataset.showSavingsBadge !== "true") return; const titleEl = groupElement.querySelector(".subscription-picker__title"); if (!titleEl) return; let badge = titleEl.querySelector("[data-savings-badge]"); const regular = Number(variantData?.price); const sub = Number(allocation?.price); if (!(regular > 0) || !(sub < regular)) { if (badge) badge.hidden = true; return; } const discountType = allocation?.discount_type || ""; let label; if (discountType === "percentage") { label = `- ${Math.round(((regular - sub) * 100) / regular)}%`; } else { const savingsMoney = allocation?.savings_amount_formatted || formatCurrency(regular - sub, picker); label = `- ${savingsMoney}`; } if (!badge) { badge = document.createElement("span"); badge.className = "subscription-picker__badge"; badge.setAttribute("data-savings-badge", ""); titleEl.appendChild(badge); } badge.hidden = false; badge.textContent = label; } function syncSellingPlanToExternalForm(picker, sellingPlanId) { const cartForm = getCartForm(picker); if (!cartForm) return; let sellingPlanInput = cartForm.querySelector('input[name="selling_plan"]'); if (sellingPlanId) { if (!sellingPlanInput) { sellingPlanInput = Object.assign(document.createElement("input"), { type: "hidden", name: "selling_plan", }); cartForm.appendChild(sellingPlanInput); } sellingPlanInput.value = String(sellingPlanId); } else { sellingPlanInput?.remove(); } } function getCartForm(picker) { const productId = picker.dataset.productId; if (!productId) return null; return Array.from(document.querySelectorAll('form[action*="cart/add"]')).find( (form) => form.querySelector('button[type="submit"], input[type="submit"]') && String(form.querySelector('input[name="product-id"]')?.value) === String(productId), ) || null; } function setPurchaseButtonsDisabled(picker, disabled) { const cartForm = getCartForm(picker); if (!cartForm) return; cartForm .querySelectorAll('button[type="submit"], input[type="submit"]') .forEach(function (button) { if (disabled) { if (!button.disabled) button.dataset.subscriptionPickerDisabled = "true"; button.disabled = true; button.setAttribute("aria-disabled", "true"); button.style.pointerEvents = "none"; return; } if (button.dataset.subscriptionPickerDisabled === "true") { button.disabled = false; button.removeAttribute("aria-disabled"); button.style.removeProperty("pointer-events"); delete button.dataset.subscriptionPickerDisabled; } }); } function setSelectedGroupAndPlan(picker, groupId, sellingPlanId) { const sellingPlanInput = picker.querySelector('input[name="selling_plan"]'); if (sellingPlanInput) sellingPlanInput.value = sellingPlanId ? String(sellingPlanId) : ""; syncSellingPlanToExternalForm(picker, sellingPlanId || null); const keepVisibleWhenOnetime = groupId === "onetime"; picker.classList.toggle( "subscription-picker--content-visible", keepVisibleWhenOnetime, ); picker .querySelectorAll("[data-subscription-group]") .forEach(function (groupElement) { const isSelected = groupElement.dataset.groupId === groupId; groupElement.classList.toggle( "subscription-picker__group--selected", isSelected, ); const isSubscriptionGroup = groupElement.dataset.groupId !== "onetime"; const contentVisible = isSelected || (keepVisibleWhenOnetime && isSubscriptionGroup); const contentElement = groupElement.querySelector( ".subscription-picker__content", ); if (contentElement) { contentElement.style.maxHeight = contentVisible ? "1000px" : "0"; contentElement.style.opacity = contentVisible ? "1" : "0"; contentElement.style.visibility = contentVisible ? "visible" : "hidden"; } }); } function clearSellingPlanSelection(picker) { const sellingPlanInput = picker.querySelector('input[name="selling_plan"]'); if (sellingPlanInput) sellingPlanInput.value = ""; syncSellingPlanToExternalForm(picker, null); } function setPickerOptionsDisabled(picker, disabled) { picker.dataset.variantUnavailable = disabled ? "true" : "false"; picker .querySelectorAll( "[data-one-time-radio], [data-group-radio], [data-selling-plan-select], [data-selling-plan-radio]", ) .forEach(function (control) { control.disabled = disabled; if (disabled) control.setAttribute("aria-disabled", "true"); else control.removeAttribute("aria-disabled"); }); } function applyVariantState(picker, variantId) { if (!variantId || !String(variantId).trim()) return; const variantAvailable = isVariantAvailable(picker, variantId); setPickerOptionsDisabled(picker, !variantAvailable); if (!variantAvailable) { setPurchaseButtonsDisabled(picker, true); clearSellingPlanSelection(picker); picker .querySelectorAll("[data-subscription-group]") .forEach(function (groupElement) { groupElement.style.removeProperty("display"); }); updateOneTimePrice(picker, variantId); refreshMainPrice(picker, variantId, null); return; } const allocationsMap = parseJSON(picker.dataset.variantAllocations); const allocations = getVariantAllocations(picker, allocationsMap, variantId); const oneTimeRadio = picker.querySelector("[data-one-time-radio]"); if (!allocations.length) { const requiresSellingPlan = picker.dataset.requiresSellingPlan === "true"; setPurchaseButtonsDisabled(picker, requiresSellingPlan); picker .querySelectorAll("[data-subscription-group]") .forEach(function (groupElement) { if (groupElement.dataset.groupId === "onetime" && !requiresSellingPlan) groupElement.style.removeProperty("display"); else groupElement.style.display = "none"; }); if (oneTimeRadio && !requiresSellingPlan) { oneTimeRadio.checked = true; setSelectedGroupAndPlan(picker, "onetime", null); } else { clearSellingPlanSelection(picker); } updateOneTimePrice(picker, variantId); refreshMainPrice(picker, variantId, null); return; } if (isUnifiedMode(picker)) { const subscriptionGroup = picker.querySelector( '[data-subscription-group][data-group-id="subscription"]', ); if (oneTimeRadio) { oneTimeRadio.closest("[data-subscription-group]")?.style.removeProperty("display"); } if (subscriptionGroup) subscriptionGroup.style.removeProperty("display"); const firstAllocation = allocations[0]; const planId = String(firstAllocation.id); const regularPrice = getVariantData( parseJSON(picker.dataset.variantData), variantId, )?.price; updateSubscriptionPriceDisplay( picker, subscriptionGroup, variantId, firstAllocation, ); const showSelector = allocations.length > 1 && picker.dataset.showOnlyFirstPlan !== "true"; const planContainer = subscriptionGroup; const singlePlanNameEl = planContainer?.querySelector( "[data-single-plan-name]", ); const planSelectorEl = planContainer?.querySelector( "[data-plan-selector]", ); if (singlePlanNameEl) { singlePlanNameEl.textContent = firstAllocation.name; singlePlanNameEl.setAttribute("aria-hidden", String(showSelector)); } if (planSelectorEl) planSelectorEl.setAttribute("aria-hidden", String(!showSelector)); const selectEl = planContainer?.querySelector( "[data-selling-plan-select]", ); if (selectEl) { selectEl.replaceChildren( ...allocations.map(function (allocation) { const option = new Option(allocation.name, allocation.id); option.dataset.sellingPlanId = allocation.id; option.dataset.savingsLabel = planLabel( allocation, regularPrice, picker, ); return option; }), ); selectEl.disabled = allocations.length <= 1; selectEl.classList.toggle( "subscription-picker__select--single", allocations.length === 1, ); } const planRadios = planContainer ? planContainer.querySelectorAll("[data-selling-plan-radio]") : []; planRadios.forEach(function (radio) { const allocation = allocations.find( (item) => String(item.id) === String(radio.value), ); radio.parentElement.style.display = allocation ? "" : "none"; const radioLabel = picker.querySelector(`label[for="${radio.id}"]`); if (allocation && radioLabel) radioLabel.textContent = planLabel(allocation, regularPrice, picker); }); const groupRadio = picker.querySelector( '[data-group-radio][value="subscription"]', ); if (groupRadio) groupRadio.checked = true; if (planContainer) { const planRadio = planContainer.querySelector( `[data-selling-plan-radio][value="${planId}"]`, ); if (selectEl?.options.length) selectEl.value = planId; if (planRadio) planRadio.checked = true; } setSelectedGroupAndPlan(picker, "subscription", planId); updateOneTimePrice(picker, variantId); refreshMainPrice(picker, variantId, planId); return; } // Original multi-group mode picker .querySelectorAll("[data-subscription-group]") .forEach(function (groupElement) { const groupId = groupElement.dataset.groupId; if (groupId === "onetime") { if (requiresSellingPlan) groupElement.style.display = "none"; else if (oneTimeRadio) groupElement.style.removeProperty("display"); else groupElement.style.display = "none"; return; } const groupAllocations = allocations.filter( (allocation) => String(allocation.group_id) === String(groupId), ); if (!groupAllocations.length) { groupElement.style.display = "none"; return; } groupElement.style.removeProperty("display"); const firstAllocation = groupAllocations[0]; updateSubscriptionPriceDisplay( picker, groupElement, variantId, firstAllocation, ); const showSelector = groupAllocations.length > 1 && picker.dataset.showOnlyFirstPlan !== "true"; const planContainer = groupElement; const singlePlanNameEl = planContainer?.querySelector( "[data-single-plan-name]", ); const planSelectorEl = planContainer?.querySelector( "[data-plan-selector]", ); if (singlePlanNameEl) { singlePlanNameEl.textContent = firstAllocation.name; singlePlanNameEl.setAttribute("aria-hidden", String(showSelector)); } if (planSelectorEl) planSelectorEl.setAttribute("aria-hidden", String(!showSelector)); const selectEl = planContainer?.querySelector( "[data-selling-plan-select]", ); if (selectEl) { selectEl.replaceChildren( ...groupAllocations.map(function (allocation) { const option = new Option(allocation.name, allocation.id); option.dataset.sellingPlanId = allocation.id; return option; }), ); selectEl.disabled = groupAllocations.length <= 1; selectEl.classList.toggle( "subscription-picker__select--single", groupAllocations.length === 1, ); } const planRadios = planContainer ? planContainer.querySelectorAll("[data-selling-plan-radio]") : []; planRadios.forEach(function (radio) { radio.parentElement.style.display = groupAllocations.some( (allocation) => String(allocation.id) === String(radio.value), ) ? "" : "none"; }); }); const firstAllocation = allocations[0]; const groupId = String(firstAllocation.group_id); const planId = String(firstAllocation.id); const groupRadio = picker.querySelector( `[data-group-radio][value="${groupId}"]`, ); if (groupRadio) groupRadio.checked = true; const groupElement = picker.querySelector( `[data-subscription-group][data-group-id="${groupId}"]`, ); const planContainer = groupElement; if (planContainer) { const selectEl = planContainer.querySelector( "[data-selling-plan-select]", ); const planRadio = planContainer.querySelector( `[data-selling-plan-radio][value="${planId}"]`, ); if (selectEl?.options.length) selectEl.value = planId; if (planRadio) planRadio.checked = true; } setSelectedGroupAndPlan(picker, groupId, planId); updateOneTimePrice(picker, variantId); refreshMainPrice(picker, variantId, planId); } function bindPicker(picker) { const sellingPlanInput = picker.querySelector('input[name="selling_plan"]'); const allocationsMap = parseJSON(picker.dataset.variantAllocations); let lastVariantId = normalizeVariantId( new URLSearchParams(location.search).get("variant"), ); applyThemeControlStyles(picker); function getCurrentVariantId() { return normalizeVariantId(new URLSearchParams(location.search).get("variant")); } // URL-based variant detection (popstate + pushState/replaceState patch) function onUrlVariantChange() { const variantId = new URLSearchParams(location.search).get("variant"); if (!variantId) return; const normalizedId = normalizeVariantId(variantId); if (!normalizedId || normalizedId === normalizeVariantId(lastVariantId)) return; lastVariantId = variantId; setTimeout( () => requestAnimationFrame(() => applyVariantState(picker, normalizedId)), 0, ); } window.addEventListener("popstate", onUrlVariantChange); if (!window._spHistoryPatch) { window._spHistoryPatch = true; ["pushState", "replaceState"].forEach(function (method) { const original = history[method].bind(history); history[method] = function () { original.apply(history, arguments); window.dispatchEvent(new Event("sp:urlchange")); }; }); } window.addEventListener("sp:urlchange", onUrlVariantChange); picker._onUrlVariantChange = onUrlVariantChange; // One-time radio picker.querySelectorAll("[data-one-time-radio]").forEach(function (radio) { radio.addEventListener("change", function () { if (!radio.checked || radio.disabled) return; setSelectedGroupAndPlan(picker, "onetime", null); refreshMainPrice(picker, getCurrentVariantId(), null); }); }); // Group radio picker.querySelectorAll("[data-group-radio]").forEach(function (radio) { radio.addEventListener("change", function () { if (!radio.checked || radio.disabled) return; const groupId = radio.value; const groupElement = picker.querySelector( `[data-subscription-group][data-group-id="${groupId}"]`, ); const planContainer = groupElement; const variantId = getCurrentVariantId(); const allocations = getVariantAllocations( picker, allocationsMap, variantId, ); const firstForGroup = groupId === "subscription" ? allocations[0] : allocations.find( (allocation) => String(allocation.group_id) === String(groupId), ); const planId = planContainer?.querySelector("[data-selling-plan-select]")?.value || planContainer?.querySelector("[data-selling-plan-radio]")?.value || String(firstForGroup?.id || ""); setSelectedGroupAndPlan(picker, groupId, planId || null); refreshMainPrice(picker, variantId, planId || null); }); }); [ ["mousedown", true], ["keydown", true], ["focus", true], ["change", false], ["blur", false], ].forEach(function (entry) { picker.addEventListener( entry[0], function (e) { const select = e.target.closest?.("[data-selling-plan-select]"); if (select) toggleOptionSavings(select, entry[1]); }, true, ); }); // Click on subscription group content selects the group (except plan selector controls) picker.addEventListener("click", function (e) { const groupElement = e.target.closest("[data-subscription-group]"); if (!groupElement || groupElement.dataset.groupId === "onetime") return; if (e.target.closest(".subscription-picker__label")) return; if (!e.target.closest(".subscription-picker__content")) return; const planSelector = e.target.closest("[data-plan-selector]"); if (planSelector) { const tag = e.target.tagName && e.target.tagName.toLowerCase(); if ( tag === "select" || tag === "input" || e.target.closest(".subscription-picker__plan-label") ) return; } const groupId = groupElement.dataset.groupId; const groupRadio = picker.querySelector( `[data-group-radio][value="${groupId}"]`, ); if (!groupRadio || groupRadio.checked || groupRadio.disabled) return; groupRadio.checked = true; groupRadio.dispatchEvent(new Event("change", { bubbles: true })); }); // Plan select / plan radio function onPlanSelected(planId) { if (picker.dataset.variantUnavailable === "true") return; if (sellingPlanInput) sellingPlanInput.value = planId; syncSellingPlanToExternalForm(picker, planId || null); const variantId = getCurrentVariantId(); const selectedAllocation = getVariantAllocations( picker, allocationsMap, variantId, ).find((allocation) => String(allocation.id) === String(planId)); const selectedGroup = picker.querySelector( ".subscription-picker__group--selected", ); if (selectedGroup && selectedAllocation) { updateSubscriptionPriceDisplay( picker, selectedGroup, variantId, selectedAllocation, ); } refreshMainPrice(picker, variantId, planId || null); } picker .querySelectorAll("[data-selling-plan-select]") .forEach(function (selectEl) { selectEl.addEventListener("change", () => onPlanSelected(selectEl.value), ); }); picker .querySelectorAll("[data-selling-plan-radio]") .forEach(function (radio) { radio.addEventListener("change", function () { if (radio.checked) onPlanSelected(radio.value); }); }); // Initialise content panel visibility picker .querySelectorAll("[data-subscription-group]") .forEach(function (groupElement) { const contentElement = groupElement.querySelector( ".subscription-picker__content", ); if (!contentElement) return; const isSelected = groupElement.classList.contains( "subscription-picker__group--selected", ); contentElement.style.maxHeight = isSelected ? "1000px" : "0"; contentElement.style.opacity = isSelected ? "1" : "0"; contentElement.style.visibility = isSelected ? "visible" : "hidden"; }); // Initial price sync setTimeout( () => requestAnimationFrame(function () { const initialVariantId = getCurrentVariantId(); if (initialVariantId) applyVariantState(picker, initialVariantId); else syncSellingPlanToExternalForm( picker, sellingPlanInput?.value || null, ); }), 0, ); // Re-apply subscription price when the theme rewrites price elements after an async // variant fetch. Observes both the elements (text updates) and their parents (replacements). const priceTargets = Array.from( document.querySelectorAll(PRICE_SELECTOR), ).filter( (el) => !el.closest(PICKER_SELECTOR) && !el.closest("s") && !el.closest("del") && !el.querySelector(PRICE_SELECTOR + "," + COMPARE_SELECTOR) && !isPriceBadge(el), ); if (priceTargets.length) { let busy = false; const observer = new MutationObserver(function () { if (busy) return; const planId = picker.querySelector('input[name="selling_plan"]')?.value || null; const variantId = getCurrentVariantId(); if (!variantId) return; busy = true; requestAnimationFrame(function () { refreshMainPrice(picker, variantId, planId); requestAnimationFrame(() => { busy = false; }); }); }); priceTargets.forEach((el) => observer.observe(el, { childList: true, characterData: true, subtree: true, }), ); new Set( priceTargets .map((el) => el.parentElement) .filter((el) => el && !el.closest(PICKER_SELECTOR)), ).forEach((el) => observer.observe(el, { childList: true })); } } function cleanupPicker(picker) { if (!picker._onUrlVariantChange) return; window.removeEventListener("popstate", picker._onUrlVariantChange); window.removeEventListener("sp:urlchange", picker._onUrlVariantChange); picker._onUrlVariantChange = null; } function init() { document .querySelectorAll( "[data-subscription-picker]:not([data-subscription-picker-bound])", ) .forEach(function (picker) { picker.setAttribute("data-subscription-picker-bound", ""); bindPicker(picker); }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); setTimeout(init, 500); document.addEventListener("shopify:section:load", function (ev) { ev.target ?.querySelectorAll("[data-subscription-picker-bound]") .forEach(function (picker) { cleanupPicker(picker); picker.removeAttribute("data-subscription-picker-bound"); }); init(); }); document.addEventListener("shopify:section:reorder", init); })();

App Block or custom implementation

If none of the files or structures above exist, your store is likely using either:

  • The Yari Flow App Block
  • A custom Plan Picker implementation

Move the Liquid Component: yariflow-plan-picker.liquid

1. Copy the component files

Copy each file from the existing theme and add it to the corresponding folder in the new theme.

Maintain the same filenames and folder structure.

2. Copy the render statement

Find where the Plan Picker is rendered in the previous theme. This is usually inside the main product form. The code will include:

{% render 'yariflow-plan-picker' %}

Copy the complete render statement from the previous theme rather than adding only the basic line.

The existing implementation may pass additional parameters that are required for the Plan Picker to work correctly.

Move the Yari Flow App Block

If your store uses the Yariflow App Block, follow the Yari Flow App Block Installation Guide to add it to the new theme.

In most cases, this involves opening the Shopify theme editor and adding the Purchase options - Yari Flow app block to the relevant product templates.

Remember to review and copy the app block settings from the previous theme, including the product form ID and any customised labels or display options.

Test the new theme

After moving the Plan Picker, preview the new theme and confirm that:

  • The Yari Flow Plan Picker appears on eligible subscription products
  • One-time and subscription options are displayed correctly
  • The selected product variant updates correctly
  • Variant-specific prices update correctly
  • Subscription products can be added to the cart
  • The selected selling plan appears in the cart or checkout
  • One-time purchases do not receive a selling plan
  • The Plan Picker works across all relevant product templates

For additional help moving Yari Flow code to a new theme, contact the Yari Flow Support team at help@yariflow.com.