{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Marquee: an endless strip of text items and/or logos. Text ticker, announcement strip or logo wall. Pure CSS animation: the item group is repeated enough times to cover a very wide screen twice, and the track slides by exactly half its width, so the loop has no seam. Screen readers get the first group only; every copy is aria-hidden and its links are out of the tab order. With prefers-reduced-motion the strip stops and the single group wraps onto several lines. An optional pause button (tiny custom element) lets visitors stop the motion. Depends on: snippets/lib-image.liquid {%- endcomment -%} {%- liquid assign s = section.settings assign eager = false if section.index <= 2 assign eager = true endif comment Estimate the width of one item group in px, so the group is repeated enough times to fill wide screens and the loop runs at roughly the chosen speed. endcomment case s.text_size when 'small' assign fs = 14 when 'medium' assign fs = 20 when 'large' assign fs = 32 when 'xlarge' assign fs = 56 else assign fs = 96 endcase assign cw = fs | times: 5 | divided_by: 10 | floor if s.uppercase assign cw = fs | times: 6 | divided_by: 10 | floor endif assign sep_px = 0 case s.separator when 'dot' assign sep_px = fs | divided_by: 2 | floor when 'star' assign sep_px = fs when 'custom' assign sep_chars = s.separator_text | size assign sep_px = sep_chars | times: cw endcase if s.separator != 'none' assign sep_px = sep_px | plus: s.gap endif assign group_px = 0 for block in section.blocks if block.type == 'image' assign h = s.logo_height if block.settings.custom_height assign h = block.settings.height endif assign ratio = 3 if block.settings.image != blank and block.settings.image.aspect_ratio assign ratio = block.settings.image.aspect_ratio endif assign w = h | times: ratio | ceil else assign chars = block.settings.text | strip_html | size if block.settings.icon != blank or block.settings.icon_image != blank assign chars = chars | plus: 2 endif assign w = chars | times: cw endif assign group_px = group_px | plus: w | plus: s.gap | plus: sep_px endfor if group_px < 1 assign group_px = 1 endif assign reps = 3200 | plus: group_px | minus: 1 | divided_by: group_px | floor if reps < 1 assign reps = 1 elsif reps > 16 assign reps = 16 endif assign copies = reps | times: 2 assign half_px = reps | times: group_px assign duration = half_px | times: 1.0 | divided_by: s.speed | round: 1 assign tilt = s.tilt | default: '0' -%}
{%- if section.blocks.size > 0 -%}
{%- for copy in (1..copies) -%}
    1 %} aria-hidden="true" {% endif %} > {%- for block in section.blocks -%} {%- liquid assign b = block.settings assign tag = 'span' if b.link != blank assign tag = 'a' endif -%}
  • <{{ tag }} class="lib-marquee__content" {% if tag == 'a' %} href="{{ b.link }}" {% if b.new_tab %} target="_blank" rel="noopener" {% endif %} {% if copy > 1 %} tabindex="-1" {% endif %} {% endif %} > {%- if block.type == 'image' -%} {%- else -%} {%- if b.icon_image != blank -%} {{ b.icon_image | image_url: width: 96 | image_tag: class: 'lib-marquee__icon lib-marquee__icon--img', loading: 'lazy', alt: '' }} {%- elsif b.icon != blank -%} {%- endif -%} {{ b.text }} {%- endif -%} {%- case s.separator -%} {%- when 'dot' -%} {%- when 'star' -%} {%- when 'custom' -%} {%- if s.separator_text != blank -%} {%- endif -%} {%- endcase -%}
  • {%- endfor -%}
{%- endfor -%}
{%- if s.pause_button -%} {%- endif -%} {%- endif -%}
{% stylesheet %} .lib-marquee { background: var(--lib-section-bg); color: var(--lib-text); padding: var(--lib-pt) 0 var(--lib-pb); overflow-x: clip; } @media (max-width: 749px) { .lib-marquee { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-marquee[data-show='desktop'] { display: none; } } @media (min-width: 750px) { .lib-marquee[data-show='mobile'] { display: none; } } /* The band is a little wider than the screen so a tilted strip has no visible ends. */ .lib-marquee__band { position: relative; background-color: var(--lib-bg); background-image: var(--lib-bg-image, none); padding-block: var(--lib-band-py); transform: rotate(var(--lib-tilt)); } .lib-marquee[data-tilted] .lib-marquee__band { margin-inline: -4vw; } .lib-marquee[data-border-top] .lib-marquee__band { border-top: 1px solid var(--lib-border); } .lib-marquee[data-border-bottom] .lib-marquee__band { border-bottom: 1px solid var(--lib-border); } .lib-marquee__viewport { overflow: hidden; } .lib-marquee[data-fade] .lib-marquee__viewport { -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); } .lib-marquee__track { display: flex; width: max-content; animation: lib-marquee-scroll var(--lib-duration) linear infinite; } .lib-marquee[data-direction='right'] .lib-marquee__track { animation-direction: reverse; } .lib-marquee[data-hover-pause] .lib-marquee__viewport:hover .lib-marquee__track, .lib-marquee__viewport:focus-within .lib-marquee__track, .lib-marquee[data-paused] .lib-marquee__track { animation-play-state: paused; } @keyframes lib-marquee-scroll { to { transform: translateX(-50%); } } .lib-marquee__group { display: flex; flex-shrink: 0; align-items: center; margin: 0; padding: 0; list-style: none; } .lib-marquee__item { display: flex; flex-shrink: 0; align-items: center; gap: var(--lib-gap); padding-inline-end: var(--lib-gap); white-space: nowrap; } /* Type: desktop size, then a separate mobile size. */ .lib-marquee__band { --lib-fs: 1.25rem; font-size: var(--lib-fs); font-weight: var(--lib-weight); line-height: 1.15; } .lib-marquee[data-size='small'] .lib-marquee__band { --lib-fs: 0.875rem; } .lib-marquee[data-size='medium'] .lib-marquee__band { --lib-fs: 1.25rem; } .lib-marquee[data-size='large'] .lib-marquee__band { --lib-fs: 2rem; } .lib-marquee[data-size='xlarge'] .lib-marquee__band { --lib-fs: clamp(2.5rem, 4.5vw, 3.5rem); } .lib-marquee[data-size='huge'] .lib-marquee__band { --lib-fs: clamp(3rem, 7.5vw, 6rem); } @media (max-width: 749px) { .lib-marquee[data-size-m='small'] .lib-marquee__band { --lib-fs: 0.8125rem; } .lib-marquee[data-size-m='medium'] .lib-marquee__band { --lib-fs: 1.0625rem; } .lib-marquee[data-size-m='large'] .lib-marquee__band { --lib-fs: 1.5rem; } .lib-marquee[data-size-m='xlarge'] .lib-marquee__band { --lib-fs: 2rem; } .lib-marquee[data-size-m='huge'] .lib-marquee__band { --lib-fs: 2.75rem; } } .lib-marquee[data-upper] .lib-marquee__band { text-transform: uppercase; letter-spacing: 0.04em; } .lib-marquee[data-heading-font] .lib-marquee__band { font-family: var(--font-heading-family, inherit); font-style: var(--font-heading-style, normal); } .lib-marquee__content { display: inline-flex; align-items: center; gap: 0.45em; color: inherit; text-decoration: none; } a.lib-marquee__content:hover .lib-marquee__text { text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 0.06em; } a.lib-marquee__content:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; border-radius: 2px; } .lib-marquee__text p { display: inline; margin: 0; } .lib-marquee__icon { line-height: 1; } .lib-marquee__icon--img { width: 1.1em; height: 1.1em; object-fit: contain; } .lib-marquee__sep { display: inline-flex; align-items: center; justify-content: center; color: var(--lib-accent); } .lib-marquee__sep--dot { width: 0.3em; height: 0.3em; border-radius: 50%; background: currentColor; } .lib-marquee__sep--star svg { width: 0.75em; height: 0.75em; } /* Logos keep their own proportions at a fixed height. */ .lib-marquee__logo { display: block; height: var(--lib-logo-h); } .lib-marquee__img { display: block; height: 100%; width: auto; max-width: none; object-fit: contain; transition: filter 0.3s ease, opacity 0.3s ease; } .lib-marquee__img:is(svg) { aspect-ratio: 3 / 1; fill: color-mix(in srgb, var(--lib-text) 25%, transparent); border-radius: 4px; } .lib-marquee[data-gray] .lib-marquee__img { filter: grayscale(1); opacity: 0.65; } .lib-marquee[data-gray] .lib-marquee__item--image:hover .lib-marquee__img { filter: none; opacity: 1; } /* Pause button */ .lib-marquee__pause { position: absolute; inset-block: 0; inset-inline-end: 0; display: flex; align-items: center; padding-inline: 2.5rem 0.75rem; background: linear-gradient(90deg, transparent, var(--lib-bg) 55%); } .lib-marquee[data-tilted] .lib-marquee__pause { inset-inline-end: 4vw; } .lib-marquee__pause:not(:defined) { display: none; } .lib-marquee__pause-btn { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; padding: 0; border: 1px solid color-mix(in srgb, var(--lib-text) 30%, transparent); border-radius: 50%; background: var(--lib-bg); color: var(--lib-text); cursor: pointer; } .lib-marquee__pause-btn:hover { border-color: currentColor; } .lib-marquee__pause-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } .lib-marquee__play-icon, .lib-marquee__pause-btn[aria-pressed='true'] .lib-marquee__pause-icon { display: none; } .lib-marquee__pause-btn[aria-pressed='true'] .lib-marquee__play-icon { display: block; } /* Reduced motion: no animation; one group, wrapped and centred. */ @media (prefers-reduced-motion: reduce) { .lib-marquee__track { animation: none; width: auto; justify-content: center; padding-inline: 1rem; } .lib-marquee__group { flex-wrap: wrap; justify-content: center; row-gap: 0.5em; flex-shrink: 1; } .lib-marquee__group[aria-hidden] { display: none; } .lib-marquee__item { white-space: normal; } .lib-marquee__item:last-child { padding-inline-end: 0; } .lib-marquee__item:last-child .lib-marquee__sep { display: none; } .lib-marquee[data-fade] .lib-marquee__viewport { -webkit-mask-image: none; mask-image: none; } .lib-marquee__pause { display: none; } .lib-marquee__img { transition: none; } } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-marquee-pause')) { customElements.define( 'lib-marquee-pause', class extends HTMLElement { connectedCallback() { const button = this.querySelector('button'); const root = this.closest('.lib-marquee'); if (!button || !root || this.bound) return; this.bound = true; button.addEventListener('click', () => { const paused = button.getAttribute('aria-pressed') !== 'true'; button.setAttribute('aria-pressed', String(paused)); button.setAttribute('aria-label', paused ? 'Play the scrolling text' : 'Pause the scrolling text'); root.toggleAttribute('data-paused', paused); }); } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Marquee", "tag": "section", "class": "lib-section", "max_blocks": 16, "settings": [ { "type": "header", "content": "Motion" }, { "type": "range", "id": "speed", "label": "Speed (pixels per second)", "min": 10, "max": 200, "step": 10, "unit": "px", "default": 60 }, { "type": "select", "id": "direction", "label": "Direction", "options": [ { "value": "left", "label": "Right to left" }, { "value": "right", "label": "Left to right" } ], "default": "left" }, { "type": "checkbox", "id": "pause_on_hover", "label": "Pause on hover", "default": true }, { "type": "checkbox", "id": "pause_button", "label": "Show pause button", "info": "Lets visitors stop the motion (recommended for accessibility). The strip also pauses while a link in it has keyboard focus, and stays still for visitors who turn off animations.", "default": true }, { "type": "select", "id": "tilt", "label": "Tilt", "info": "Rotates the strip. Add top and bottom padding so the corners are not cut off.", "options": [ { "value": "-3", "label": "-3°" }, { "value": "-2", "label": "-2°" }, { "value": "-1", "label": "-1°" }, { "value": "0", "label": "None" }, { "value": "1", "label": "1°" }, { "value": "2", "label": "2°" }, { "value": "3", "label": "3°" } ], "default": "0" }, { "type": "header", "content": "Separator" }, { "type": "select", "id": "separator", "label": "Separator between items", "options": [ { "value": "none", "label": "None" }, { "value": "dot", "label": "Dot" }, { "value": "star", "label": "Star" }, { "value": "custom", "label": "Custom text" } ], "default": "star" }, { "type": "text", "id": "separator_text", "label": "Separator text", "default": "/", "visible_if": "{{ section.settings.separator == 'custom' }}" }, { "type": "header", "content": "Text" }, { "type": "select", "id": "text_size", "label": "Text size on desktop", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" }, { "value": "xlarge", "label": "Extra large" }, { "value": "huge", "label": "Huge" } ], "default": "large" }, { "type": "select", "id": "text_size_mobile", "label": "Text size on mobile", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" }, { "value": "xlarge", "label": "Extra large" }, { "value": "huge", "label": "Huge" } ], "default": "medium" }, { "type": "select", "id": "font_weight", "label": "Font weight", "options": [ { "value": "400", "label": "Regular" }, { "value": "500", "label": "Medium" }, { "value": "600", "label": "Semibold" }, { "value": "700", "label": "Bold" }, { "value": "800", "label": "Extra bold" } ], "default": "600" }, { "type": "checkbox", "id": "uppercase", "label": "Uppercase", "default": false }, { "type": "checkbox", "id": "heading_font", "label": "Use the theme's heading font", "info": "Works in themes that define --font-heading-family (Dawn and most free themes). Otherwise the body font is used.", "default": false }, { "type": "header", "content": "Layout" }, { "type": "range", "id": "gap", "label": "Space between items", "min": 8, "max": 120, "step": 4, "unit": "px", "default": 32 }, { "type": "range", "id": "band_padding", "label": "Strip inner padding", "min": 0, "max": 64, "step": 2, "unit": "px", "default": 18 }, { "type": "range", "id": "logo_height", "label": "Logo height", "min": 16, "max": 120, "step": 4, "unit": "px", "default": 40 }, { "type": "checkbox", "id": "grayscale", "label": "Show logos in grayscale, color on hover", "default": true }, { "type": "checkbox", "id": "fade_edges", "label": "Fade the left and right edges", "default": false }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Strip background", "default": "#1C1917" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Strip background gradient", "info": "Optional. Covers the strip background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#FAFAF9" }, { "type": "color", "id": "color_accent", "label": "Separator", "default": "#F59E0B" }, { "type": "color", "id": "color_section_bg", "label": "Section background", "info": "Optional. Shows above and below the strip, for example around a tilted strip." }, { "type": "checkbox", "id": "border_top", "label": "Border on top", "default": false }, { "type": "checkbox", "id": "border_bottom", "label": "Border on bottom", "default": false }, { "type": "color", "id": "color_border", "label": "Border", "default": "#44403C" }, { "type": "header", "content": "Spacing and visibility" }, { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 0 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 0 }, { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 0 }, { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 0 }, { "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": "text", "name": "Text item", "settings": [ { "type": "inline_richtext", "id": "text", "label": "Text", "default": "Free shipping over $75" }, { "type": "text", "id": "icon", "label": "Icon or emoji", "info": "Optional. One character shown before the text." }, { "type": "image_picker", "id": "icon_image", "label": "Icon image", "info": "Optional. Replaces the icon character." }, { "type": "url", "id": "link", "label": "Link" }, { "type": "checkbox", "id": "new_tab", "label": "Open link in a new tab", "default": false, "visible_if": "{{ block.settings.link != blank }}" } ] }, { "type": "image", "name": "Logo or image", "settings": [ { "type": "image_picker", "id": "image", "label": "Image", "info": "Alt text comes from the image. A transparent PNG or SVG logo works best." }, { "type": "checkbox", "id": "custom_height", "label": "Use a custom height", "default": false }, { "type": "range", "id": "height", "label": "Height", "min": 16, "max": 160, "step": 4, "unit": "px", "default": 48, "visible_if": "{{ block.settings.custom_height }}" }, { "type": "url", "id": "link", "label": "Link" }, { "type": "checkbox", "id": "new_tab", "label": "Open link in a new tab", "default": false, "visible_if": "{{ block.settings.link != blank }}" } ] } ], "presets": [ { "name": "Lib · Marquee", "blocks": [ { "type": "text", "settings": { "text": "Free shipping over $75" } }, { "type": "text", "settings": { "text": "Hand-thrown in small batches" } }, { "type": "text", "settings": { "text": "30-day returns" } }, { "type": "text", "settings": { "text": "New: the Linen collection" } } ] } ] } {% endschema %}