{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Announcement bar: one static message, or several that rotate (fade or slide) with optional arrows. Each message has an optional icon or emoji, link and short "ends" note. Dismissible with a close button that is remembered per browser; change the campaign version to show it again. All messages share one grid cell, so the bar is as tall as its longest message from the first paint and nothing shifts when the script starts. Optional sticky at the top of the page. Depends on: nothing. {%- endcomment -%} {%- liquid assign s = section.settings assign count = section.blocks.size assign rotating = false if s.mode == 'rotating' and count > 1 assign rotating = true endif assign arrows = false if rotating and s.show_arrows assign arrows = true endif assign version = s.dismiss_version | default: '1' | handleize assign dismiss_key = 'lib-announce:' | append: section.id | append: ':' | append: version -%} {%- if s.sticky -%} {%- endif -%}
{%- if arrows -%} {%- endif -%}
{%- for block in section.blocks -%} {%- unless rotating or forloop.first -%}{%- continue -%}{%- endunless -%} {%- assign b = block.settings -%} {%- assign wrap_link = false -%} {%- if b.link != blank and b.link_label == blank -%} {%- unless b.message contains ' {%- if wrap_link -%}{%- endif -%} {%- if b.icon == 'emoji' and b.emoji != blank -%} {%- elsif b.icon != 'none' and b.icon != 'emoji' -%} {%- endif -%} {{ b.message }} {%- if wrap_link -%}{%- endif -%} {%- if b.ends_text != blank -%} {{ b.ends_text | escape }} {%- endif -%} {%- if b.link != blank and b.link_label != blank -%} {{ b.link_label | escape }} {%- endif -%}
{%- endfor -%}
{%- if arrows -%} {%- endif -%} {%- if s.dismissible -%} {%- endif -%} {%- if s.dismissible and request.design_mode != true -%} {%- endif -%} {% stylesheet %} .lib-announce { background-color: var(--lib-bg); background-image: var(--lib-bg-image, none); color: var(--lib-text); padding: var(--lib-pt) 1rem var(--lib-pb); font-size: 0.875rem; line-height: 1.4; } .lib-announce[hidden] { display: none; } .lib-announce--small { font-size: 0.8125rem; } .lib-announce--large { font-size: 1rem; } .lib-announce[data-border] { border-bottom: 1px solid var(--lib-border); } @media (max-width: 749px) { .lib-announce { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-announce[data-show='desktop'] { display: none; } } @media (min-width: 750px) { .lib-announce[data-show='mobile'] { display: none; } } .lib-announce__inner { position: relative; display: flex; align-items: center; gap: 0.5rem; max-width: 1200px; margin: 0 auto; } .lib-announce--full .lib-announce__inner { max-width: none; } /* Keep the message clear of the close button, on both sides so centred text stays centred. */ .lib-announce[data-dismissible] .lib-announce__inner { padding-inline: 2.25rem; } .lib-announce__viewport { flex: 1; min-width: 0; overflow: hidden; overflow: clip; } /* Every message sits in the same cell: the bar reserves the tallest one's height. */ .lib-announce__track { display: grid; } .lib-announce__slide { grid-area: 1 / 1; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; column-gap: 0.5em; row-gap: 0.15em; min-width: 0; text-align: left; visibility: hidden; } .lib-announce__slide[data-active] { visibility: visible; } .lib-announce__inner[data-align='center'] .lib-announce__slide { justify-content: center; text-align: center; } .lib-announce__inner[data-align='right'] .lib-announce__slide { justify-content: flex-end; text-align: right; } @media (max-width: 749px) { .lib-announce__inner[data-align-m='left'] .lib-announce__slide { justify-content: flex-start; text-align: left; } .lib-announce__inner[data-align-m='center'] .lib-announce__slide { justify-content: center; text-align: center; } } .lib-announce__message > p { display: inline; margin: 0; } .lib-announce a { color: var(--lib-accent); text-underline-offset: 0.2em; } .lib-announce .lib-announce__wrap-link { display: inline-flex; align-items: center; gap: 0.5em; color: inherit; text-decoration: none; } .lib-announce__wrap-link .lib-announce__message { text-decoration: underline; text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); transition: text-decoration-color 0.2s; } .lib-announce__wrap-link:hover .lib-announce__message { text-decoration-color: currentColor; } .lib-announce__cta { font-weight: 600; text-decoration: underline; } .lib-announce__cta:hover { text-decoration-thickness: 2px; } .lib-announce__icon { flex: none; width: 1.15em; height: 1.15em; } .lib-announce__emoji { flex: none; line-height: 1; } .lib-announce__ends { padding: 0.1em 0.55em; border-radius: 999px; font-size: 0.85em; background: color-mix(in srgb, currentColor 14%, transparent); white-space: nowrap; } .lib-announce__arrow, .lib-announce__close { flex: none; display: grid; place-items: center; width: 1.75rem; height: 1.75rem; padding: 0; border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer; opacity: 0.75; transition: opacity 0.2s, background-color 0.2s; } .lib-announce__arrow:hover, .lib-announce__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } .lib-announce__arrow svg, .lib-announce__close svg { width: 1rem; height: 1rem; } .lib-announce__close { position: absolute; inset-inline-end: 0; top: 50%; translate: 0 -50%; } .lib-announce__arrow:dir(rtl) svg { transform: scaleX(-1); } .lib-announce a:focus-visible, .lib-announce__arrow:focus-visible, .lib-announce__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; opacity: 1; } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-announcement-bar')) { customElements.define( 'lib-announcement-bar', class extends HTMLElement { connectedCallback() { if (this.ready) return; this.ready = true; this.bar = this.closest('.lib-announce'); this.closeButton = this.querySelector('[data-close]'); if (this.closeButton) this.closeButton.addEventListener('click', () => this.dismiss()); this.slides = Array.from(this.querySelectorAll('[data-slide]')); this.track = this.querySelector('[data-track]'); if (!this.hasAttribute('data-rotating') || this.slides.length < 2 || !this.track) return; this.index = Math.max(0, this.slides.findIndex((slide) => slide.hasAttribute('data-active'))); this.delay = Math.max(2, Number(this.dataset.interval) || 5) * 1000; this.reduced = matchMedia('(prefers-reduced-motion: reduce)'); this.hovered = false; this.focused = false; this.querySelectorAll('.lib-announce__arrow').forEach((button) => button.addEventListener('click', () => this.go(this.index + Number(button.dataset.dir), Number(button.dataset.dir))) ); this.addEventListener('mouseenter', () => { this.hovered = true; this.sync(); }); this.addEventListener('mouseleave', () => { this.hovered = false; this.sync(); }); this.addEventListener('focusin', () => { this.focused = true; this.sync(); }); this.addEventListener('focusout', (event) => { if (this.contains(event.relatedTarget)) return; this.focused = false; this.sync(); }); this.onVisibility = () => this.sync(); document.addEventListener('visibilitychange', this.onVisibility); this.onMotion = () => this.sync(); this.reduced.addEventListener?.('change', this.onMotion); // Theme editor: show the selected message and hold it there. this.addEventListener('shopify:block:select', (event) => { const i = this.slides.indexOf(event.target); if (i > -1) this.go(i, 0, true); this.editing = true; this.sync(); }); this.addEventListener('shopify:block:deselect', () => { this.editing = false; this.sync(); }); this.sync(); } disconnectedCallback() { clearInterval(this.timer); this.timer = null; if (this.onVisibility) document.removeEventListener('visibilitychange', this.onVisibility); if (this.onMotion) this.reduced.removeEventListener?.('change', this.onMotion); } // Rotate only when nothing asks it to stop; screen readers hear changes only while it is still. sync() { const run = !this.reduced.matches && !this.hovered && !this.focused && !this.editing && !document.hidden; this.track.setAttribute('aria-live', run ? 'off' : 'polite'); if (run && !this.timer) { this.timer = setInterval(() => this.go(this.index + 1, 1), this.delay); } else if (!run && this.timer) { clearInterval(this.timer); this.timer = null; } } go(target, dir = 1, instant = false) { const count = this.slides.length; const next = ((target % count) + count) % count; if (next === this.index) return; const from = this.slides[this.index]; const to = this.slides[next]; from.removeAttribute('data-active'); from.setAttribute('inert', ''); to.setAttribute('data-active', ''); to.removeAttribute('inert'); this.index = next; // Restart the countdown so a manual step gets a full interval. if (this.timer) { clearInterval(this.timer); this.timer = null; this.sync(); } if (instant || this.reduced.matches || !from.animate) return; const slide = this.bar && this.bar.dataset.transition === 'slide'; const rtl = getComputedStyle(this).direction === 'rtl' ? -1 : 1; const shift = (dir || 1) * rtl * 40; const timing = { duration: 450, easing: 'cubic-bezier(0.2, 0.7, 0.2, 1)' }; from.animate( [ { visibility: 'visible', opacity: 1, transform: 'none' }, { visibility: 'visible', opacity: 0, transform: slide ? `translateX(${-shift}px)` : 'none' }, ], timing ); to.animate( [ { opacity: 0, transform: slide ? `translateX(${shift}px)` : 'none' }, { opacity: 1, transform: 'none' }, ], timing ); } dismiss() { const key = this.dataset.dismissKey; try { if (key) localStorage.setItem(key, '1'); } catch (e) {} clearInterval(this.timer); this.timer = null; if (this.bar) this.bar.hidden = true; } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Announcement bar", "tag": "section", "class": "lib-section", "max_blocks": 6, "settings": [ { "type": "header", "content": "Behaviour" }, { "type": "select", "id": "mode", "label": "Mode", "options": [ { "value": "static", "label": "Static (first message only)" }, { "value": "rotating", "label": "Rotate through messages" } ], "default": "rotating" }, { "type": "select", "id": "transition", "label": "Transition", "options": [ { "value": "fade", "label": "Fade" }, { "value": "slide", "label": "Slide" } ], "default": "slide", "visible_if": "{{ section.settings.mode == 'rotating' }}" }, { "type": "range", "id": "interval", "label": "Time per message", "info": "Rotation pauses on hover and keyboard focus, and is off when the visitor prefers reduced motion.", "min": 3, "max": 12, "step": 1, "unit": "s", "default": 5, "visible_if": "{{ section.settings.mode == 'rotating' }}" }, { "type": "checkbox", "id": "show_arrows", "label": "Show previous and next arrows", "default": true, "visible_if": "{{ section.settings.mode == 'rotating' }}" }, { "type": "checkbox", "id": "dismissible", "label": "Visitors can close the bar", "info": "Remembered in the visitor's browser.", "default": false }, { "type": "text", "id": "dismiss_version", "label": "Campaign version", "info": "Change this (for example to \"spring-sale\") to show the bar again to visitors who closed it.", "default": "1", "visible_if": "{{ section.settings.dismissible }}" }, { "type": "checkbox", "id": "sticky", "label": "Stick to the top while scrolling", "info": "Check it does not overlap a sticky header in your theme.", "default": false }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "text_size", "label": "Text size", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" } ], "default": "medium" }, { "type": "text_alignment", "id": "text_alignment", "label": "Alignment on desktop", "default": "center" }, { "type": "select", "id": "text_alignment_mobile", "label": "Alignment on mobile", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" } ], "default": "center" }, { "type": "select", "id": "section_width", "label": "Width", "options": [ { "value": "page", "label": "Page width" }, { "value": "full", "label": "Full width" } ], "default": "page" }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Background", "default": "#1C1917" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#FAFAF9" }, { "type": "color", "id": "color_link", "label": "Links", "default": "#F5D0A9" }, { "type": "checkbox", "id": "border_bottom", "label": "Bottom border", "default": false }, { "type": "color", "id": "color_border", "label": "Border", "default": "#44403C", "visible_if": "{{ section.settings.border_bottom }}" }, { "type": "header", "content": "Spacing and visibility" }, { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 40, "step": 1, "unit": "px", "default": 10 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 40, "step": 1, "unit": "px", "default": 10 }, { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 40, "step": 1, "unit": "px", "default": 8 }, { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 40, "step": 1, "unit": "px", "default": 8 }, { "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": "text", "id": "anchor_id", "label": "Anchor ID", "info": "Optional. Link to this section with #your-id." } ], "blocks": [ { "type": "message", "name": "Message", "limit": 6, "settings": [ { "type": "inline_richtext", "id": "message", "label": "Message", "default": "Free delivery on orders over $75" }, { "type": "select", "id": "icon", "label": "Icon", "options": [ { "value": "none", "label": "None" }, { "value": "truck", "label": "Delivery truck" }, { "value": "gift", "label": "Gift" }, { "value": "tag", "label": "Price tag" }, { "value": "star", "label": "Star" }, { "value": "leaf", "label": "Leaf" }, { "value": "clock", "label": "Clock" }, { "value": "sparkle", "label": "Sparkle" }, { "value": "return", "label": "Returns" }, { "value": "emoji", "label": "Emoji" } ], "default": "none" }, { "type": "text", "id": "emoji", "label": "Emoji", "default": "✨", "visible_if": "{{ block.settings.icon == 'emoji' }}" }, { "type": "url", "id": "link", "label": "Link" }, { "type": "text", "id": "link_label", "label": "Link label", "info": "Optional. Leave empty to make the whole message the link.", "visible_if": "{{ block.settings.link != blank }}" }, { "type": "text", "id": "ends_text", "label": "Ends note", "info": "Optional short note, for example \"Ends Sunday\". No timer." } ] } ], "presets": [ { "name": "Lib · Announcement bar", "blocks": [ { "type": "message", "settings": { "message": "Free delivery on orders over $75", "icon": "truck" } }, { "type": "message", "settings": { "message": "Autumn edit: 20% off linen bedding", "icon": "tag", "link_label": "Shop the edit", "ends_text": "Ends Sunday" } }, { "type": "message", "settings": { "message": "Gift wrapping is free on every order", "icon": "gift" } } ] } ] } {% endschema %}