Hi, is it possible to make custom button like the picture below? When we click subscribe and save, it will show the purchase option for subscribe and save (delivery frequency and so on). Then we click one-time purchase, it will show our normal purchase details from shopify. Is it possible?
I already surveying few apps but most of them didnt offer the design like this
this design is possible, but it requires either checkout extensions (Shopify Plus only) or custom development. That’s why most standard apps can’t deliver it their UI is locked to their built-in patterns.
What plan are you on? That’s the real constraint here.
{%- assign cw_product = closest.product | default: product -%}
{%- if cw_product.selling_plan_groups.size > 0 -%}
{%- assign cw_group = cw_product.selling_plan_groups | where: 'name', 'Subscribe & Save' | first -%}
{%- unless cw_group -%}{%- assign cw_group = cw_product.selling_plan_groups.first -%}{%- endunless -%}
{%- assign cw_uid = block.id | default: section.id -%}
<style>
.cw-po{--cw-accent:#2f5d54;--cw-soft:#f3efe9;margin:0 0 1.1rem;font-family:inherit}
.cw-po__seg{display:flex;border-radius:12px;overflow:hidden;border:1px solid rgba(0,0,0,.08)}
.cw-po__btn{flex:1;padding:.85rem 1rem;border:0;background:var(--cw-soft);cursor:pointer;font:inherit;font-weight:600;color:#2a2a2a;line-height:1.25;text-align:center;transition:background .15s,color .15s}
.cw-po__btn small{display:block;font-weight:500;font-size:.78em;opacity:.8;margin-top:.1rem}
.cw-po__btn.is-active{background:var(--cw-accent);color:#fff}
.cw-po__body{margin-top:.85rem}
.cw-po__price{font-size:1.6rem;font-weight:700;color:var(--cw-accent);line-height:1.1}
.cw-po__note{font-size:.9rem;color:#555;margin-top:.2rem}
.cw-po__freq{margin-top:.7rem}
.cw-po__freq label{display:block;font-size:.8rem;color:#555;margin-bottom:.3rem}
.cw-po__freq select{width:100%;padding:.6rem .7rem;border:1px solid rgba(0,0,0,.22);border-radius:8px;font:inherit;background:#fff}
.cw-po__freq[hidden]{display:none}
</style>
<div class="cw-po" data-cw-po data-section="{{ cw_uid }}">
<div class="cw-po__seg">
<button type="button" class="cw-po__btn is-active" data-mode="subscription">
Subscribe and Save<small data-cw-save></small>
</button>
<button type="button" class="cw-po__btn" data-mode="onetime">
One time purchase<small>Standard price</small>
</button>
</div>
<div class="cw-po__body">
<div class="cw-po__price" data-cw-price></div>
<div class="cw-po__note" data-cw-note></div>
<div class="cw-po__freq" data-cw-freq>
<label for="cw-freq-{{ cw_uid }}">Delivery frequency</label>
<select id="cw-freq-{{ cw_uid }}" data-cw-plan>
{%- for plan in cw_group.selling_plans -%}
<option value="{{ plan.id }}">{{ plan.name }}</option>
{%- endfor -%}
</select>
</div>
</div>
</div>
<script>
(function(){
var DATA = {
{%- for variant in cw_product.variants -%}
"{{ variant.id }}": {
oneTime: {{ variant.price | json }},
oneTimeStr: {{ variant.price | money | json }},
plans: {
{%- for alloc in variant.selling_plan_allocations -%}
"{{ alloc.selling_plan.id }}": { price: {{ alloc.price | json }}, priceStr: {{ alloc.price | money | json }}, name: {{ alloc.selling_plan.name | json }} }{% unless forloop.last %},{% endunless %}
{%- endfor -%}
}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
};
var SECTION = {{ cw_uid | json }};
function init(){
var root = document.querySelector('[data-cw-po][data-section="'+SECTION+'"]');
if(!root || root.dataset.cwInit) return;
var form = (root.closest('form[data-type="add-to-cart-form"]'))
|| (root.closest('.shopify-section, product-information, .product, [class*="product"]') || document).querySelector('form[data-type="add-to-cart-form"]')
|| document.querySelector('form[data-type="add-to-cart-form"]');
if(!form) return;
root.dataset.cwInit = "1";
var variantInput = form.querySelector('input[name="id"]');
var planSelect = root.querySelector('[data-cw-plan]');
var priceEl = root.querySelector('[data-cw-price]');
var noteEl = root.querySelector('[data-cw-note]');
var freqEl = root.querySelector('[data-cw-freq]');
var saveEl = root.querySelector('[data-cw-save]');
var btns = root.querySelectorAll('.cw-po__btn');
var mode = 'subscription';
function vData(){
var keys = Object.keys(DATA);
var vid = variantInput && DATA[variantInput.value] ? variantInput.value : keys[0];
return DATA[vid] || {};
}
function planInput(create){
var i = form.querySelector('input[name="selling_plan"][data-cw]');
if(!i && create){ i=document.createElement('input'); i.type='hidden'; i.name='selling_plan'; i.setAttribute('data-cw',''); form.appendChild(i); }
return i;
}
function saveText(vd, id){
var p=vd.plans&&vd.plans[id]; if(!p||!vd.oneTime) return '';
var pct=Math.round((1-(p.price/vd.oneTime))*1000)/10;
return pct>0?(' · save '+pct+'%'):'';
}
function render(){
var vd=vData();
var id=planSelect?planSelect.value:(vd.plans?Object.keys(vd.plans)[0]:'');
btns.forEach(function(b){var on=b.dataset.mode===mode;b.classList.toggle('is-active',on);});
if(mode==='subscription'){
freqEl.hidden=false;
var p=vd.plans&&vd.plans[id];
priceEl.textContent=p?p.priceStr:(vd.oneTimeStr||'');
var freqText=planSelect&&planSelect.selectedIndex>-1?planSelect.options[planSelect.selectedIndex].textContent:'';
if(freqText.indexOf(',')>-1) freqText=freqText.slice(freqText.lastIndexOf(',')+1).trim();
noteEl.textContent=freqText;
if(saveEl) saveEl.textContent=saveText(vd,id);
var inp=planInput(true); if(inp) inp.value=id;
} else {
freqEl.hidden=true;
priceEl.textContent=vd.oneTimeStr||'';
noteEl.textContent='One-time purchase';
var ex=planInput(false); if(ex) ex.remove();
}
}
btns.forEach(function(b){b.addEventListener('click',function(){mode=b.dataset.mode;render();});});
if(planSelect) planSelect.addEventListener('change',render);
form.addEventListener('change',function(){setTimeout(render,60);});
render();
}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',init);
else init();
document.addEventListener('shopify:section:load',init);
})();
</script>
{%- endif -%}
Notes:
It reads your subscription app’s plans automatically. Whatever delivery options you set up (weekly, monthly, and so on) appear in the frequency dropdown.
Subscribe shows the discounted price and the frequency picker. One time shows the normal price and hides the picker.
Change the accent colour on the first CSS line (--cw-accent).
Horizon still shows its own price above the widget. Leave it, or hide the theme price block on the product if you want only the widget’s price.
{%- assign cw_product = closest.product | default: product -%}
{%- if cw_product.selling_plan_groups.size > 0 -%}
{%- assign cw_group = cw_product.selling_plan_groups | where: 'name', 'Subscribe & Save' | first -%}
{%- unless cw_group -%}{%- assign cw_group = cw_product.selling_plan_groups.first -%}{%- endunless -%}
{%- assign cw_uid = block.id | default: section.id -%}
<style>
.cw-po{--cw-accent:#2f5d54;--cw-line:rgba(0,0,0,.14);margin:0 0 1rem;font-family:inherit}
.cw-po__seg{display:flex;gap:.5rem}
.cw-po__btn{flex:1;display:flex;align-items:center;justify-content:center;gap:.4rem;
padding:.55rem .6rem;border:1px solid var(--cw-line);border-radius:8px;background:#fff;
cursor:pointer;font:inherit;font-weight:600;font-size:.9rem;color:#2a2a2a;line-height:1.2;
text-align:center;transition:border-color .15s,box-shadow .15s,background .15s}
.cw-po__btn small{font-weight:600;font-size:.72em;color:var(--cw-accent)}
.cw-po__btn.is-active{border-color:var(--cw-accent);box-shadow:inset 0 0 0 1px var(--cw-accent);background:#fff}
.cw-po__btn.is-active small{color:var(--cw-accent)}
.cw-po__body{margin-top:.6rem}
.cw-po__price{font-size:1.15rem;font-weight:700;color:var(--cw-accent);line-height:1.1}
.cw-po__note{font-size:.8rem;color:#666;margin-top:.15rem}
.cw-po__freq{margin-top:.55rem}
.cw-po__freq label{display:block;font-size:.75rem;color:#666;margin-bottom:.25rem}
.cw-po__freq select{width:100%;padding:.5rem .6rem;border:1px solid var(--cw-line);border-radius:7px;font:inherit;background:#fff}
.cw-po__freq[hidden],.cw-po__ot[hidden]{display:none}
.cw-po__ot{margin-top:.6rem}
</style>
<div class="cw-po" data-cw-po data-section="{{ cw_uid }}">
<div class="cw-po__seg">
<button type="button" class="cw-po__btn is-active" data-mode="subscription">
Subscribe and Save <small data-cw-save></small>
</button>
<button type="button" class="cw-po__btn" data-mode="onetime">
One time
</button>
</div>
<div class="cw-po__body">
<div class="cw-po__price" data-cw-price></div>
<div class="cw-po__note" data-cw-note></div>
<div class="cw-po__freq" data-cw-freq>
<label for="cw-freq-{{ cw_uid }}">Delivery frequency</label>
<select id="cw-freq-{{ cw_uid }}" data-cw-plan>
{%- for plan in cw_group.selling_plans -%}
<option value="{{ plan.id }}">{{ plan.name }}</option>
{%- endfor -%}
</select>
</div>
{%- comment -%} Shows only when "One time" is selected. Paste your installment HTML between the two marks. {%- endcomment -%}
<div class="cw-po__ot" data-cw-ot hidden>
<!-- INSTALLMENT HTML START -->
<!-- INSTALLMENT HTML END -->
</div>
</div>
</div>
<script>
(function(){
var DATA = {
{%- for variant in cw_product.variants -%}
"{{ variant.id }}": {
oneTime: {{ variant.price | json }},
oneTimeStr: {{ variant.price | money | json }},
plans: {
{%- for alloc in variant.selling_plan_allocations -%}
"{{ alloc.selling_plan.id }}": { price: {{ alloc.price | json }}, priceStr: {{ alloc.price | money | json }}, name: {{ alloc.selling_plan.name | json }} }{% unless forloop.last %},{% endunless %}
{%- endfor -%}
}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
};
var SECTION = {{ cw_uid | json }};
function init(){
var root = document.querySelector('[data-cw-po][data-section="'+SECTION+'"]');
if(!root || root.dataset.cwInit) return;
var form = (root.closest('form[data-type="add-to-cart-form"]'))
|| (root.closest('.shopify-section, product-information, .product, [class*="product"]') || document).querySelector('form[data-type="add-to-cart-form"]')
|| document.querySelector('form[data-type="add-to-cart-form"]');
if(!form) return;
root.dataset.cwInit = "1";
var variantInput = form.querySelector('input[name="id"]');
var planSelect = root.querySelector('[data-cw-plan]');
var priceEl = root.querySelector('[data-cw-price]');
var noteEl = root.querySelector('[data-cw-note]');
var freqEl = root.querySelector('[data-cw-freq]');
var otEl = root.querySelector('[data-cw-ot]');
var saveEl = root.querySelector('[data-cw-save]');
var btns = root.querySelectorAll('.cw-po__btn');
var mode = 'subscription';
function vData(){
var keys = Object.keys(DATA);
var vid = variantInput && DATA[variantInput.value] ? variantInput.value : keys[0];
return DATA[vid] || {};
}
function planInput(create){
var i = form.querySelector('input[name="selling_plan"][data-cw]');
if(!i && create){ i=document.createElement('input'); i.type='hidden'; i.name='selling_plan'; i.setAttribute('data-cw',''); form.appendChild(i); }
return i;
}
function saveText(vd, id){
var p=vd.plans&&vd.plans[id]; if(!p||!vd.oneTime) return '';
var pct=Math.round((1-(p.price/vd.oneTime))*1000)/10;
return pct>0?('save '+pct+'%'):'';
}
function render(){
var vd=vData();
var id=planSelect?planSelect.value:(vd.plans?Object.keys(vd.plans)[0]:'');
btns.forEach(function(b){var on=b.dataset.mode===mode;b.classList.toggle('is-active',on);});
if(mode==='subscription'){
freqEl.hidden=false; if(otEl) otEl.hidden=true;
var p=vd.plans&&vd.plans[id];
priceEl.textContent=p?p.priceStr:(vd.oneTimeStr||'');
var freqText=planSelect&&planSelect.selectedIndex>-1?planSelect.options[planSelect.selectedIndex].textContent:'';
if(freqText.indexOf(',')>-1) freqText=freqText.slice(freqText.lastIndexOf(',')+1).trim();
noteEl.textContent=freqText;
if(saveEl) saveEl.textContent=saveText(vd,id);
var inp=planInput(true); if(inp) inp.value=id;
} else {
freqEl.hidden=true; if(otEl) otEl.hidden=false;
priceEl.textContent=vd.oneTimeStr||'';
noteEl.textContent='One-time purchase';
if(saveEl) saveEl.textContent='';
var ex=planInput(false); if(ex) ex.remove();
}
}
btns.forEach(function(b){b.addEventListener('click',function(){mode=b.dataset.mode;render();});});
if(planSelect) planSelect.addEventListener('change',render);
form.addEventListener('change',function(){setTimeout(render,60);});
render();
}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',init);
else init();
document.addEventListener('shopify:section:load',init);
})();
</script>
{%- endif -%}
Adjust the size
Button text size: the font-size:.9rem on .cw-po__btn
Price size: the font-size:1.15rem on .cw-po__price
Accent colour: the --cw-accent value on the first CSS line
Tested on a Horizon store: the slim selector keeps Subscribe and One time in one row, and the installment box renders only in One time with the subscription plan removed from the cart.