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
Yes, this should be possible with custom code, and the Subscribe & Save and One time Purchase options can be combined into a single purchase selector like the design in your screenshot.
Before I provide the code, could you share your storefront URL and, if the store is password protected, the storefront password as well?
I’d like to check your current theme and subscription setup first, because the exact implementation will depend on how your product form and subscription selling plans are currently configured.
Once I can see the setup, I can provide code that combines both options properly so selecting Subscribe & Save displays the subscription details such as delivery frequency, while selecting One time Purchase switches back to the normal Shopify purchase options.
If you found this answer helpful, you can mark it as the solution.
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.
Thanks! I can see that you’re using the Horizon theme, and adding a Custom Liquid block is a good starting point.
Before I give you the code, could you tell me which subscription app you’re using (or send me the app link)?
The reason I’m asking is that the Subscribe & Save options are handled through the app’s Shopify selling plans, so I want to make sure the code connects to your existing subscription setup correctly rather than giving you generic code that may not work with your app.
Once you send me the subscription app name, I can give you the exact code to add to the Custom Liquid block so the two options work together:
Subscribe & Save → shows the available delivery frequency/subscription options.
One time Purchase → switches back to the normal Shopify purchase option.
Then we can style it to match the design you originally showed.
Yes, this is possible. You can build the two options as a custom purchase selector, with Subscriber & Save revealing the subscription frequency/options and One-time Purchase revealing the normal Shopify purchase flow.
The important part is that the subscription option needs to be connected to a subscription app/payment integration that supports Shopify selling plans. The visual design itself can generally be handled with theme code/CSS, even if the subscription app doesn’t provide this exact layout out of the box.
The setup would typically be:
Subscribe & Save → selected by default or when clicked → show delivery frequency and subscription price.
One-time Purchase → hide the subscription fields → show the standard Shopify price/purchase option.
Both options use the same product/variant, but subscription selection adds the appropriate selling plan to the cart.
The styling can then be matched to the design in your screenshot with custom Liquid/CSS/JavaScript.
So you don’t necessarily need an app that offers that exact visual design. What matters more is that the subscription app supports Shopify selling plans and allows the purchase widget to be customised or integrated into the theme.
If you’re comfortable editing the theme, this is quite achievable without rebuilding the entire product page.
{%- 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.