{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Newsletter popup: an offer with Shopify's customer form (the email is added as a customer tagged "newsletter" plus a tag you choose), an optional image and an optional discount code with a copy button. It never shows on first paint: it opens after a delay, when the pointer leaves the top of the window (desktop only) and/or after the shopper scrolls part of the page, whichever comes first. On screens below 750px it is a small card at the bottom of the screen that leaves the page usable, never a full-screen overlay; on desktop a centered modal dialog or a corner card. Closes with the close button, Esc or a click outside; a closed offer stays away for the days you set, a sign-up for good, and it shows at most once per visit. After a sign-up the page reloads with the success message in the popup. Without JavaScript it does not appear at all. The discount itself is created in Shopify (Discounts); this section only shows the code. Depends on: none {%- endcomment -%} {%- liquid assign s = section.settings assign heading_tag = s.heading_tag | default: 'h2' assign form_id = 'lib-popup-form-' | append: section.id assign title_id = 'LibPopupTitle-' | append: section.id assign input_id = 'LibPopupEmail-' | append: section.id assign error_id = 'LibPopupError-' | append: section.id assign consent_id = 'LibPopupConsent-' | append: section.id assign tags = 'newsletter' if s.customer_tag != blank assign tags = tags | append: ',' | append: s.customer_tag endif assign offer_key = s.heading | strip_html | append: '|' | append: s.discount_code assign has_image = false if s.image != blank assign has_image = true endif -%} {%- if s.image != blank -%}
{{ s.image | image_url: width: 900 | image_tag: class: 'lib-popup__img', loading: 'lazy', sizes: '360px', widths: '360, 540, 720, 900' }}
{%- endif -%}
<{{ heading_tag }} class="lib-popup__heading lib-popup__heading--{{ s.heading_size }}" id="{{ title_id }}"> {{- s.heading | default: 'Newsletter' -}} {%- if s.text != blank -%}
{{ s.text }}
{%- endif -%} {%- if s.discount_code != blank -%}
{{ s.code_label | escape }} {{ s.discount_code | escape }}
{%- endif -%} {%- form 'customer', id: form_id, class: 'lib-popup__form' -%} {%- if form.posted_successfully -%}

{{ s.success_message | escape }}

{%- else -%} {%- if form.errors -%} {%- endif -%}
{%- if s.consent != blank -%} {%- endif -%} {%- endif -%} {%- endform -%}
{% stylesheet %} .lib-popup { display: contents; } .lib-popup__dialog { box-sizing: border-box; width: min(100vw - 2rem, 46rem); max-height: calc(100vh - 2rem); margin: auto; padding: 0; border: 0; border-radius: 16px; overflow: auto; background-color: var(--lib-bg); background-image: var(--lib-bg-image, none); color: var(--lib-text); box-shadow: 0 30px 80px -30px rgb(0 0 0 / 0.45); } .lib-popup__dialog[open] { display: grid; grid-template-columns: 1fr; } .lib-popup__dialog[open][data-has-image='true'] { grid-template-columns: 2fr 3fr; } .lib-popup__dialog::backdrop { background: rgb(12 10 9 / var(--lib-backdrop-o, 0.5)); } .lib-popup__dialog:not([data-has-image='true']) { width: min(100vw - 2rem, 30rem); } /* Non-modal cards: the desktop corner card and, below 750px, the bottom card. */ .lib-popup__dialog[data-mode='corner'], .lib-popup__dialog[data-mode='sheet'] { position: fixed; z-index: 2147483000; inset: auto 1.25rem 1.25rem auto; margin: 0; width: min(100vw - 2.5rem, 24rem); } .lib-popup__dialog[data-mode='corner'][open], .lib-popup__dialog[data-mode='sheet'][open] { grid-template-columns: 1fr; } .lib-popup__dialog[data-mode='corner'] .lib-popup__media, .lib-popup__dialog[data-mode='sheet'] .lib-popup__media { display: none; } .lib-popup__dialog[data-mode='sheet'] { inset: auto 0.75rem 0.75rem 0.75rem; width: auto; max-height: 45vh; border-radius: 14px; } .lib-popup__dialog[data-mode='sheet'] .lib-popup__body { padding: 1.125rem 1.125rem 1rem; } .lib-popup__dialog[data-mode='sheet'] .lib-popup__text { font-size: 0.9375rem; margin-top: 0.375rem; } .lib-popup__dialog[data-mode='sheet'] .lib-popup__heading { font-size: 1.25rem; padding-right: 2.5rem; } .lib-popup__media { position: relative; min-height: 100%; background: color-mix(in srgb, var(--lib-text) 8%, transparent); } .lib-popup__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } .lib-popup__body { display: flex; flex-direction: column; align-items: flex-start; text-align: left; padding: clamp(1.5rem, 4vw, 2.5rem); } .lib-popup__body[data-align='center'] { align-items: center; text-align: center; } .lib-popup__body[data-align='right'] { align-items: flex-end; text-align: right; } @media (max-width: 749px) { .lib-popup__body[data-align-m='left'] { align-items: flex-start; text-align: left; } .lib-popup__body[data-align-m='center'] { align-items: center; text-align: center; } .lib-popup__body[data-align-m='right'] { align-items: flex-end; text-align: right; } } .lib-popup__close { position: absolute; top: 0.625rem; right: 0.625rem; z-index: 1; display: grid; place-items: center; width: 2.5rem; height: 2.5rem; border: 0; border-radius: 50%; background: color-mix(in srgb, var(--lib-bg) 85%, transparent); color: var(--lib-text); cursor: pointer; } .lib-popup__close:hover { background: color-mix(in srgb, var(--lib-text) 10%, var(--lib-bg)); } .lib-popup__close:focus-visible, .lib-popup__copy:focus-visible, .lib-popup__submit:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; } .lib-popup__heading { margin: 0; padding-right: 2rem; font-size: clamp(1.5rem, 2.6vw, 2rem); line-height: 1.15; letter-spacing: -0.02em; text-wrap: balance; } .lib-popup__heading--small { font-size: 1.25rem; } .lib-popup__heading--large { font-size: clamp(1.75rem, 3.2vw, 2.5rem); } .lib-popup__text { margin-top: 0.75rem; font-size: 1rem; line-height: 1.55; opacity: 0.85; } .lib-popup__text > :first-child { margin-top: 0; } .lib-popup__text > :last-child { margin-bottom: 0; } .lib-popup__code { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin-top: 1rem; padding: 0.625rem 0.875rem; border: 1.5px dashed color-mix(in srgb, var(--lib-text) 40%, transparent); border-radius: 10px; } .lib-popup__code-label { font-size: 0.875rem; opacity: 0.8; } .lib-popup__code-value { font-weight: 700; letter-spacing: 0.08em; } .lib-popup__copy { min-height: 2rem; padding: 0.25rem 0.75rem; border: 1.5px solid currentColor; border-radius: 999px; background: transparent; color: inherit; font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; } .lib-popup__form { width: 100%; margin-top: 1.125rem; } .lib-popup__label { display: block; margin-bottom: 0.375rem; font-size: 0.875rem; font-weight: 600; text-align: left; } .lib-popup__row { display: flex; gap: 0.5rem; } .lib-popup__input { flex: 1; min-width: 0; min-height: 2.875rem; padding: 0 0.875rem; border: 1.5px solid color-mix(in srgb, var(--lib-text) 35%, transparent); border-radius: 999px; background: #fff; color: #1c1917; font: inherit; } .lib-popup__input:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 1px; } .lib-popup__submit { flex: none; min-height: 2.875rem; padding: 0 1.25rem; border: 0; border-radius: 999px; background: var(--lib-accent); color: var(--lib-accent-text); font: inherit; font-weight: 600; cursor: pointer; } .lib-popup__consent { margin: 0.75rem 0 0; font-size: 0.8125rem; line-height: 1.45; opacity: 0.8; text-align: left; } .lib-popup__consent a { color: inherit; text-underline-offset: 0.2em; } .lib-popup__msg { margin: 0 0 0.75rem; font-weight: 600; text-align: left; } .lib-popup__msg p { margin: 0; } .lib-popup__msg--success { margin: 0; padding: 0.75rem 1rem; border-radius: 10px; background: color-mix(in srgb, var(--lib-accent) 12%, transparent); } .lib-popup__msg--error { color: #b42318; } .lib-popup__sr { position: absolute !important; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } @media (prefers-reduced-motion: no-preference) { .lib-popup[data-animate] .lib-popup__dialog[open] { animation: lib-popup-in 0.35s ease-out; } .lib-popup[data-animate] .lib-popup__dialog[data-mode='sheet'][open], .lib-popup[data-animate] .lib-popup__dialog[data-mode='corner'][open] { animation-name: lib-popup-up; } } @keyframes lib-popup-in { from { opacity: 0; transform: scale(0.97); } } @keyframes lib-popup-up { from { opacity: 0; transform: translateY(16px); } } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-newsletter-popup')) { customElements.define( 'lib-newsletter-popup', class extends HTMLElement { connectedCallback() { if (this._ready) return; this.dialog = this.querySelector('dialog'); if (!this.dialog || typeof this.dialog.showModal !== 'function') return; this._ready = true; this.storeKey = `lib-popup:${this.dataset.sectionId}`; this.cleanup = []; this.listen(this.dialog.querySelector('.lib-popup__close'), 'click', () => this.close(true)); this.listen(this.dialog, 'cancel', (event) => { event.preventDefault(); this.close(true); }); // A click on the backdrop lands on the dialog itself. this.listen(this.dialog, 'click', (event) => { if (event.target === this.dialog) this.close(true); }); // Non-modal cards don't get Esc from the browser. this.listen(document, 'keydown', (event) => { if (event.key === 'Escape' && this.dialog.open && this.dialog.dataset.mode !== 'modal') this.close(true); }); this.setupCopy(); // Shopify renders the success message only after a successful post. if (this.dialog.querySelector('.lib-popup__msg--success')) this.setAttribute('data-posted', ''); if (window.Shopify && window.Shopify.designMode) { this.setupEditor(); return; } // Back from a sign-up (or a form error): show the result right away. if (location.hash === `#${this.dataset.formId}` && (this.hasAttribute('data-posted') || this.dialog.querySelector('[role="alert"]'))) { if (this.hasAttribute('data-posted')) this.remember('signed-up', 365); this.open(); return; } if (this.isSuppressed()) return; this.setupTriggers(); } disconnectedCallback() { (this.cleanup || []).forEach((undo) => undo()); this.cleanup = []; clearTimeout(this.timer); this._ready = false; } listen(target, type, handler, options) { if (!target) return; target.addEventListener(type, handler, options); this.cleanup.push(() => target.removeEventListener(type, handler, options)); } storage(kind) { try { return kind === 'session' ? window.sessionStorage : window.localStorage; } catch (error) { return null; } } isSuppressed() { if (this.dataset.show === 'desktop' && this.isSmall()) return true; if (this.dataset.show === 'mobile' && !this.isSmall()) return true; const session = this.storage('session'); if (session && session.getItem(this.storeKey)) return true; const local = this.storage('local'); if (!local) return false; try { const saved = JSON.parse(local.getItem(this.storeKey) || 'null'); if (!saved) return false; if (saved.state === 'signed-up') return saved.until > Date.now(); return saved.offer === this.dataset.offer && saved.until > Date.now(); } catch (error) { return false; } } remember(state, days) { const local = this.storage('local'); if (!local) return; const until = Date.now() + days * 24 * 60 * 60 * 1000; try { local.setItem(this.storeKey, JSON.stringify({ state, offer: this.dataset.offer, until })); } catch (error) { /* Storage full or blocked: the popup may show again, nothing else breaks. */ } } setupTriggers() { const fire = () => this.open(); const delay = Number(this.dataset.delay); if (delay > 0) this.timer = setTimeout(fire, delay * 1000); if (this.hasAttribute('data-exit') && window.matchMedia('(hover: hover) and (pointer: fine)').matches) { this.listen(document, 'mouseout', (event) => { if (!event.relatedTarget && event.clientY <= 0) fire(); }); } const share = Number(this.dataset.scroll); if (share > 0) { this.listen( window, 'scroll', () => { const room = document.documentElement.scrollHeight - window.innerHeight; if (room > 0 && (window.scrollY / room) * 100 >= share) fire(); }, { passive: true } ); } } setupEditor() { const id = this.dataset.sectionId; this.listen(document, 'shopify:section:select', (event) => { if (event.detail && event.detail.sectionId === id) this.open(); }); this.listen(document, 'shopify:section:deselect', (event) => { if (event.detail && event.detail.sectionId === id) this.close(false); }); } setupCopy() { const button = this.querySelector('.lib-popup__copy'); if (!button || !navigator.clipboard) return; button.hidden = false; const status = this.querySelector('[data-copy-status]'); this.listen(button, 'click', () => { navigator.clipboard.writeText(button.dataset.code).then( () => { if (status) status.textContent = button.dataset.copied; }, () => { /* Clipboard refused: the code stays visible to copy by hand. */ } ); }); } isSmall() { return window.matchMedia('(max-width: 749px)').matches; } open() { if (this.dialog.open) return; clearTimeout(this.timer); const session = this.storage('session'); if (session) { try { session.setItem(this.storeKey, '1'); } catch (error) { /* Blocked storage only means it may show again on the next page. */ } } this.returnFocus = document.activeElement; const mode = this.isSmall() ? 'sheet' : this.dataset.desktop === 'corner' ? 'corner' : 'modal'; this.dialog.dataset.mode = mode; if (mode === 'modal') this.dialog.showModal(); else this.dialog.show(); } close(remember) { if (!this.dialog.open) return; this.dialog.close(); if (remember && !this.hasAttribute('data-posted')) this.remember('closed', Number(this.dataset.days) || 1); if (this.returnFocus && this.returnFocus.isConnected && typeof this.returnFocus.focus === 'function') this.returnFocus.focus(); this.returnFocus = null; } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Newsletter popup", "tag": "section", "class": "lib-section", "settings": [ { "type": "paragraph", "content": "Opens after a delay, on exit intent or after scrolling, never on page load. Below 750px it is a small card at the bottom of the screen. In the editor it shows while this section is selected." }, { "type": "header", "content": "Offer" }, { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "10% off your first order" }, { "type": "select", "id": "heading_tag", "label": "Heading tag", "options": [ { "value": "h2", "label": "H2" }, { "value": "h3", "label": "H3" }, { "value": "p", "label": "Paragraph" } ], "default": "h2" }, { "type": "select", "id": "heading_size", "label": "Heading size", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" } ], "default": "medium" }, { "type": "richtext", "id": "text", "label": "Text", "default": "

Join the studio letter for new pieces and kiln days. One email a month.

" }, { "type": "image_picker", "id": "image", "label": "Image", "info": "Optional. Desktop modal only; the small cards leave it out." }, { "type": "text", "id": "discount_code", "label": "Discount code", "info": "Optional. Create the discount in Shopify under Discounts; this only shows the code." }, { "type": "text", "id": "code_label", "label": "Code label", "default": "Use code" }, { "type": "text", "id": "copy_label", "label": "Copy button", "default": "Copy" }, { "type": "text", "id": "copied_label", "label": "Copied message", "info": "Read by screen readers after copying.", "default": "Code copied" }, { "type": "header", "content": "Form" }, { "type": "text", "id": "input_label", "label": "Field label", "default": "Email address" }, { "type": "text", "id": "placeholder", "label": "Field placeholder", "default": "you@example.com" }, { "type": "text", "id": "button_label", "label": "Button label", "default": "Subscribe" }, { "type": "text", "id": "customer_tag", "label": "Extra customer tag", "info": "Added next to \"newsletter\", so you can tell popup sign-ups apart.", "default": "popup" }, { "type": "inline_richtext", "id": "consent", "label": "Consent text", "default": "Unsubscribe any time. See our privacy policy." }, { "type": "text", "id": "success_message", "label": "Success message", "default": "Thanks for joining. Check your inbox for the code." }, { "type": "text", "id": "close_label", "label": "Close button label", "default": "Close" }, { "type": "header", "content": "When it opens" }, { "type": "checkbox", "id": "trigger_delay", "label": "After a delay", "default": true }, { "type": "range", "id": "delay_seconds", "label": "Delay", "min": 10, "max": 120, "step": 5, "unit": "s", "default": 20, "visible_if": "{{ section.settings.trigger_delay }}" }, { "type": "checkbox", "id": "trigger_exit", "label": "When the pointer leaves the window", "info": "Desktop only.", "default": true }, { "type": "checkbox", "id": "trigger_scroll", "label": "After scrolling", "default": false }, { "type": "range", "id": "scroll_percent", "label": "Scrolled share of the page", "min": 20, "max": 90, "step": 5, "unit": "%", "default": 50, "visible_if": "{{ section.settings.trigger_scroll }}" }, { "type": "range", "id": "remember_days", "label": "Stay closed for", "info": "After a shopper closes it. A sign-up hides it for a year.", "min": 1, "max": 90, "step": 1, "unit": "d", "default": 14 }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "desktop_style", "label": "On desktop", "options": [ { "value": "modal", "label": "Centered dialog" }, { "value": "corner", "label": "Corner card" } ], "default": "modal" }, { "type": "text_alignment", "id": "text_alignment", "label": "Text alignment on desktop", "default": "left" }, { "type": "select", "id": "text_alignment_mobile", "label": "Text alignment on mobile", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" }, { "value": "right", "label": "Right" } ], "default": "left" }, { "type": "select", "id": "show_on", "label": "Show on", "options": [ { "value": "all", "label": "All devices" }, { "value": "desktop", "label": "Desktop only" }, { "value": "mobile", "label": "Mobile only" } ], "default": "all" }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Background", "default": "#FFFDF9" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#2A2420" }, { "type": "color", "id": "color_accent", "label": "Accent (button, focus ring)", "default": "#2A2420" }, { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFDF9" }, { "type": "range", "id": "backdrop_opacity", "label": "Page dimming behind the dialog", "min": 0, "max": 80, "step": 5, "unit": "%", "default": 45 }, { "type": "header", "content": "Motion" }, { "type": "checkbox", "id": "animate", "label": "Fade in", "info": "Never when reduced motion is on.", "default": true } ], "presets": [ { "name": "Lib · Newsletter popup" } ] } {% endschema %}