[object Object]

← back to Dw Theme Hamburger

search: add type-ahead predictive dropdown (Shopify /search/suggest.json, product+SKU results as you type)

fc9ece54ed603db1acbfc7483b3ab1f08442b51e · 2026-07-15 10:43:57 -0700 · Steve Abrams

Files touched

Diff

commit fc9ece54ed603db1acbfc7483b3ab1f08442b51e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 15 10:43:57 2026 -0700

    search: add type-ahead predictive dropdown (Shopify /search/suggest.json, product+SKU results as you type)
---
 assets/custom.css      | 57 ++++++++++++++++++++++++++++++++++++++++++++++++
 sections/header.liquid | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/assets/custom.css b/assets/custom.css
index 8ae117b..11da53b 100644
--- a/assets/custom.css
+++ b/assets/custom.css
@@ -1491,6 +1491,63 @@ input.add-to-cart:hover,
   pointer-events: auto;   /* but the logo itself stays clickable (links home) */
 }
 
+/* ---- Predictive search dropdown (type-ahead SKU/product results) ---- */
+.hdr-suggest {
+  position: fixed;
+  z-index: 4000;
+  background: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.14);
+  border-radius: 4px;
+  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.18);
+  max-height: 74vh;
+  overflow-y: auto;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+}
+.hdr-suggest__row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 8px 12px;
+  text-decoration: none;
+  color: #141414;
+  border-bottom: 1px solid #f1f1f1;
+}
+.hdr-suggest__row:hover {
+  background: #f7f7f7;
+}
+.hdr-suggest__row img,
+.hdr-suggest__noimg {
+  width: 42px;
+  height: 42px;
+  object-fit: cover;
+  border-radius: 3px;
+  background: #eee;
+  flex: 0 0 42px;
+}
+.hdr-suggest__t {
+  font-size: 13px;
+  line-height: 1.3;
+}
+.hdr-suggest__all {
+  display: block;
+  padding: 11px 12px;
+  font-size: 12px;
+  letter-spacing: 0.04em;
+  text-transform: uppercase;
+  text-align: center;
+  text-decoration: none;
+  color: #141414;
+  font-weight: 600;
+}
+.hdr-suggest__all:hover {
+  background: #f2f2f2;
+}
+.hdr-suggest__empty {
+  padding: 14px 12px;
+  font-size: 13px;
+  color: #777;
+}
+
 /* Right cluster horizontal at ALL widths (theme only made it flex under 1080px,
    so search/camera/cart were stacking vertically on desktop) */
 .header-content-right--inline-search {
diff --git a/sections/header.liquid b/sections/header.liquid
index 4905611..e0f508b 100644
--- a/sections/header.liquid
+++ b/sections/header.liquid
@@ -350,9 +350,66 @@
     if (!toggle) return;
     function q() { return wrap.querySelector('.search-form__input'); }
     function frm() { return wrap.querySelector('form'); }
+
+    /* ---- predictive dropdown (Shopify native /search/suggest.json) ---- */
+    var box = document.createElement('div');
+    box.className = 'hdr-suggest';
+    box.style.display = 'none';
+    document.body.appendChild(box);
+    var timer = null, lastQ = '';
+    function positionBox() {
+      var input = q(); if (!input) return;
+      var r = input.getBoundingClientRect();
+      box.style.left = Math.max(8, Math.min(r.left, window.innerWidth - 300)) + 'px';
+      box.style.top = (r.bottom + 6) + 'px';
+      box.style.width = Math.max(260, r.width) + 'px';
+    }
+    function hideBox() { box.style.display = 'none'; box.innerHTML = ''; }
+    function render(products, query) {
+      var safeQ = query.replace(/[<>]/g, '');
+      if (!products.length) {
+        box.innerHTML = '<div class="hdr-suggest__empty">No matches for &ldquo;' + safeQ + '&rdquo;</div>';
+      } else {
+        box.innerHTML = products.map(function (p) {
+          var img = p.image ? '<img src="' + p.image + '" alt="" loading="lazy">' : '<span class="hdr-suggest__noimg"></span>';
+          return '<a class="hdr-suggest__row" href="' + p.url + '">' + img +
+                 '<span class="hdr-suggest__t">' + (p.title || '').replace(/[<>]/g, '') + '</span></a>';
+        }).join('') +
+        '<a class="hdr-suggest__all" href="/search?q=' + encodeURIComponent(query) + '">See all results →</a>';
+      }
+      positionBox();
+      box.style.display = 'block';
+    }
+    function fetchSuggest(query) {
+      var url = '/search/suggest.json?q=' + encodeURIComponent(query) +
+                '&resources[type]=product&resources[limit]=8&resources[options][unavailable_products]=last';
+      fetch(url, { headers: { 'Accept': 'application/json' } })
+        .then(function (r) { return r.json(); })
+        .then(function (d) {
+          if (query !== lastQ) return;
+          var prods = (((d.resources || {}).results || {}).products) || [];
+          render(prods.map(function (p) {
+            return { title: p.title, url: p.url, image: (p.featured_image && p.featured_image.url) || p.image || '' };
+          }), query);
+        }).catch(function () {});
+    }
+    function onInput() {
+      var input = q(); if (!input) return;
+      var val = input.value.trim(); lastQ = val;
+      clearTimeout(timer);
+      if (val.length < 2) { hideBox(); return; }
+      timer = setTimeout(function () { fetchSuggest(val); }, 160);
+    }
+    wrap.addEventListener('input', function (e) {
+      if (e.target && e.target.classList && e.target.classList.contains('search-form__input')) onInput();
+    });
+    window.addEventListener('resize', function () { if (box.style.display === 'block') positionBox(); });
+    window.addEventListener('scroll', function () { if (box.style.display === 'block') positionBox(); }, true);
+
     function close() {
       wrap.classList.remove('search-open');
       toggle.setAttribute('aria-expanded', 'false');
+      hideBox();
     }
     toggle.addEventListener('click', function (e) {
       e.preventDefault();
@@ -367,7 +424,7 @@
       if (input) { setTimeout(function () { input.focus(); }, 60); }
     });
     document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
-    document.addEventListener('click', function (e) { if (!wrap.contains(e.target)) close(); });
+    document.addEventListener('click', function (e) { if (!wrap.contains(e.target) && !box.contains(e.target)) close(); });
   })();
 </script>
 

← 116a2c7 header: tighten hamburger drawer (0.9rem/5px), +top padding  ·  back to Dw Theme Hamburger  ·  color-palette: hide color-index cards whose image fails to l 9530d81 →