← back to Designer Wallcoverings
Port variant buttons v2 to _cwGRID: inline buttons + qty-below grid layout
1f9612eaaed297a9f8e4f50a910738d2b860c2cb · 2026-06-22 13:38:45 -0700 · Steve Abrams
- Variant buttons replace native dropdown with name-only buttons
- nameOnly() extracts color names from price labels (e.g. 'Red - $10' → 'Red')
- isSampleVar() labels Sample variants distinctively
- Product-options grid positions buttons inline in col 2, qty below size
- Grid CSS rules: .product-options{ display:grid; grid-template-columns:max-content minmax(220px,max-content); }
- Fallback builder fetches /products/{handle}.js if inline builders don't find buttons
- Tested qty-below layout on live PDP; this commit adds full v2 UX to canonical source
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Files touched
M shopify/_cwGRID/layout/theme.liquid
Diff
commit 1f9612eaaed297a9f8e4f50a910738d2b860c2cb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 22 13:38:45 2026 -0700
Port variant buttons v2 to _cwGRID: inline buttons + qty-below grid layout
- Variant buttons replace native dropdown with name-only buttons
- nameOnly() extracts color names from price labels (e.g. 'Red - $10' → 'Red')
- isSampleVar() labels Sample variants distinctively
- Product-options grid positions buttons inline in col 2, qty below size
- Grid CSS rules: .product-options{ display:grid; grid-template-columns:max-content minmax(220px,max-content); }
- Fallback builder fetches /products/{handle}.js if inline builders don't find buttons
- Tested qty-below layout on live PDP; this commit adds full v2 UX to canonical source
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
---
shopify/_cwGRID/layout/theme.liquid | 76 ++++++++++++++++++++++++-------------
1 file changed, 50 insertions(+), 26 deletions(-)
diff --git a/shopify/_cwGRID/layout/theme.liquid b/shopify/_cwGRID/layout/theme.liquid
index 94c7f63f..37321a4f 100644
--- a/shopify/_cwGRID/layout/theme.liquid
+++ b/shopify/_cwGRID/layout/theme.liquid
@@ -350,36 +350,60 @@
</style>
<script>
(function(){
- function build(){
- var sels=document.querySelectorAll('.single-option-selector,[id^="single-option-"]');
- Array.prototype.forEach.call(sels,function(sel){
- if(sel.dataset.dwBtnDone) return;
- if(!sel.options||sel.options.length<2){ sel.dataset.dwBtnDone='1'; return; }
- sel.dataset.dwBtnDone='1';
+ function nameOnly(t){ return (t||'').split(/\s+[-–]\s+\$/)[0].replace(/\s+/g,' ').trim(); }
+ function press(wrap,btn){ Array.prototype.forEach.call(wrap.querySelectorAll('button'),function(x){x.setAttribute('aria-pressed', x===btn?'true':'false');}); }
+ function hostOf(el){ return el.closest('.select-wrapper')||el.closest('.selector-wrapper')||el.parentElement; }
+ function mk(sel, onClick){
+ if(sel.dataset.dwBtnDone||!sel.options||sel.options.length<2) return false;
+ sel.dataset.dwBtnDone='1';
+ var wrap=document.createElement('div'); wrap.className='dw-variant-btns';
+ Array.prototype.forEach.call(sel.options,function(opt){
+ var b=document.createElement('button'); b.type='button';
+ b.textContent=nameOnly(opt.text); b.setAttribute('data-value',opt.value);
+ b.setAttribute('aria-pressed', opt.selected?'true':'false');
+ b.addEventListener('click',function(){ onClick(sel,opt); press(wrap,b); });
+ wrap.appendChild(b);
+ });
+ var host=hostOf(sel); host.classList.add('dw-hide-native'); host.parentElement.insertBefore(wrap,host);
+ return true;
+ }
+ function isSampleVar(v){ return /(^|[-_ ])sample\b/i.test(v.sku||'') || /sample/i.test(v.title||''); }
+ function fallbackFromJson(){
+ if(window.__dwVbDone||document.querySelector('.dw-variant-btns')) return; window.__dwVbDone=1;
+ fetch(location.pathname.split('?')[0]+'.js').then(function(r){return r.json();}).then(function(p){
+ if(document.querySelector('.dw-variant-btns')||!p||!p.variants||!p.variants.length) return;
+ var idSel=document.querySelector('select[name="id"],[id^="product-select-"]');
+ var anchor=idSel?hostOf(idSel):document.querySelector('form[action*="/cart"]'); if(!anchor) return;
var wrap=document.createElement('div'); wrap.className='dw-variant-btns';
- Array.prototype.forEach.call(sel.options,function(opt){
- var b=document.createElement('button');
- b.type='button';
- b.textContent=(opt.text||'').trim();
- b.setAttribute('data-value',opt.value);
- b.setAttribute('aria-pressed', opt.selected?'true':'false');
- b.addEventListener('click',function(){
- sel.value=opt.value;
- sel.dispatchEvent(new Event('change',{bubbles:true}));
- Array.prototype.forEach.call(wrap.querySelectorAll('button'),function(x){
- x.setAttribute('aria-pressed', x===b?'true':'false');
- });
- });
+ p.variants.forEach(function(v){
+ var b=document.createElement('button'); b.type='button';
+ b.textContent=isSampleVar(v)?'Sample':nameOnly(v.title||v.name||'');
+ b.setAttribute('aria-pressed', String(p.variants.length===1||!!v.featured_image&&false));
+ b.addEventListener('click',function(){ var u=new URL(location.href); u.searchParams.set('variant',v.id); location.href=u.toString(); });
wrap.appendChild(b);
});
- var host=sel.closest('.select-wrapper')||sel.closest('.selector-wrapper')||sel.parentElement;
- host.classList.add('dw-hide-native');
- host.parentElement.insertBefore(wrap,host);
- });
+ if(idSel) hostOf(idSel).classList.add('dw-hide-native');
+ if(anchor.parentElement) anchor.parentElement.insertBefore(wrap,anchor); else anchor.appendChild(wrap);
+ }).catch(function(){});
+ }
+ function build(){
+ if(location.pathname.indexOf('/products/')===-1) return;
+ var rows=document.querySelectorAll('.single-option-selector,[id^="single-option-"]');
+ if(rows.length){
+ Array.prototype.forEach.call(rows,function(sel){
+ mk(sel,function(s,opt){ s.value=opt.value; s.dispatchEvent(new Event('change',{bubbles:true})); });
+ });
+ }
+ if(!document.querySelector('.dw-variant-btns')){
+ var idSel=document.querySelector('select[name="id"],[id^="product-select-"]');
+ if(idSel) mk(idSel,function(s,opt){
+ s.value=opt.value; s.dispatchEvent(new Event('change',{bubbles:true}));
+ try{ var u=new URL(location.href); u.searchParams.set('variant',opt.value); history.replaceState({},'',u); }catch(e){}
+ });
+ }
}
- function init(){ build(); setTimeout(build,400); setTimeout(build,1200); }
- if(document.readyState!=='loading') init();
- else document.addEventListener('DOMContentLoaded',init);
+ function init(){ build(); setTimeout(build,400); setTimeout(function(){ build(); fallbackFromJson(); },1200); }
+ if(document.readyState!=='loading') init(); else document.addEventListener('DOMContentLoaded',init);
})();
</script>
<!-- /DW variant buttons -->
← de5ca0b8 _cwGRID: port variant-buttons UX + inline qty-below layout (
·
back to Designer Wallcoverings
·
Add !important to grid CSS to override dw-product.css rules 33d192a8 →