Shopify Builds>LUS Brands>Press / trust logo strip with scroll-snap carousel and no JavaScript

Press / Trust Logo Strip With Scroll-Snap Carousel and No JavaScript

A blocks-based logo row for the homepage that a merchandiser can reorder, relabel and link — and that becomes a swipeable, snapping carousel on mobile through CSS alone, with no slider library and no script of its own.

A row of press and award logos that scrolls on mobile is usually a slider library's job. This homepage section does it with CSS alone: a column-flow grid sized from a --columns custom property, overflow-x scroll with mandatory scroll-snap, and a peek of the next tile so the row reads as scrollable. Each logo is a block with its own image, accessible name and link. No script, no library — technique, not a measured outcome.

The Problem

Press mentions and award logos are the trust signals a haircare brand puts near the top of its homepage, and Shopify's theme editor has no native component for a row of them. The standard theme answer is a slider: a section that loads a carousel library, initializes it on the row and lets the shopper swipe. That's a reasonable shape for a product carousel. For six static logos it means the row's layout is decided by a script after it runs, rather than by the stylesheet as the page renders, and it means the section carries a runtime dependency for what is, visually, a list of images.

The merchandising need was ordinary: add a logo, give it a link, put it in order, take it out next quarter — without a developer. The engineering question was whether that needed a library at all.

The Constraint

The row has to scroll on mobile. Six logos don't fit across a phone, and stacking them turns a strip into a wall. Whatever scrolls has to feel like a carousel — tiles that settle into position rather than stopping mid-logo — and has to tell the shopper it scrolls, because a row that fits its first two tiles neatly looks finished.

The accessible name of each linked logo has to come from somewhere the merchandiser controls. Alt text on uploaded files is often blank, and a link wrapping an image with no alt is a link with no name — a screen reader announces the URL or nothing. The section couldn't rely on the file having been labeled.

And the whole thing had to live in one section file, authored in the theme editor, with no asset of its own to enqueue.

What We Built

logo-strip.liquid defines a repeatable logo block with three settings — an image_picker, an alt_text field and a url — and ships a six-logo preset so the section looks finished the moment it's added.

The grid. The row is a CSS grid with grid-auto-flow: column and grid-auto-columns computed from a --columns custom property, so the number of tiles visible at once is a value rather than a rule. The container is overflow-x: scroll with scroll-snap-type: x mandatory; each tile carries scroll-snap-align: start and scroll-snap-stop: always, so a swipe settles on a tile boundary and can't skip past one. The scrollbar is hidden in both WebKit and Firefox. On mobile the row shows two columns plus a peeking third; at 480px it shows three plus a peek; on desktop it's a static row with every logo visible.

The peek. The column count is set so the next tile is partly visible at the edge. That fractional tile is what tells the shopper the row scrolls — there's no arrow, no dot indicator and no script to add one.

The images. Each logo is emitted through image_tag with a 120/182/240/364 srcset and a sizes expression, so the browser picks a file for the tile's rendered width. Background color arrives through a --logo-strip-bg custom property set inline from the section setting; padding is split into desktop and mobile ranges. Component CSS lives in the section's own {% stylesheet %} block.

The names. The <img> takes alt_text first and falls back to image.alt. The wrapping link's aria-label runs the fallback the other way — image.alt first, then alt_text — so whichever field the merchandiser filled in, both the image and the link end up named.

Why This Way

Scroll-snap gives the swipe affordance a shopper expects, and it gives it from the stylesheet: the layout is what the CSS says it is from the first render, with no script deciding it afterwards. The deliberate peek does the job an indicator would have done, without the indicator. For a row of static images, that's the whole requirement, and a library would have added a runtime to satisfy it.

Passing the background color through a custom property, rather than writing a scoped rule per section id, keeps the section's stylesheet the same for every instance. The value varies; the rules don't.

Control is what this gives up. A CSS carousel has no API. There's no autoplay, no programmatic "go to slide three," no event when a tile settles, and if the design later wants any of those, the answer is a library after all. There are also no arrows, so a desktop shopper who lands on a mobile-width window scrolls with the trackpad or not at all. We took that in exchange for a section with no script — and we make no claim about what that does to page performance, because nothing here was measured.

Implementation Notes

  • The accessible name falls back alt_textimage.alt for the <img>, and image.altalt_text for the wrapping link's aria-label, so both are named whichever field was filled.
  • Wrapping a logo in an <a> is optional per block; a logo with no url renders as a plain image with no empty link around it.
  • Blocks render with {{ block.shopify_attributes }}, so selecting a block in the theme editor highlights the right tile.
  • scroll-snap-stop: always is set on each tile, so a fast swipe advances one tile at a time rather than flinging past several.
  • The srcset is four steps — 120, 182, 240 and 364 — matched to the tile widths the grid actually produces at its three breakpoints.
  • Live as one instance in index.json, not disabled.

Edge Cases

  • A block with no image renders an empty grid cell rather than a broken image, so a half-configured section doesn't show a missing-file icon.
  • A blank heading is skipped, and the page-width wrapper that holds it with it, so the strip sits flush without an empty heading row.
  • Where scroll-snap is unsupported, the grid degrades to a plain horizontal scroll: the row still scrolls, it just doesn't settle on tile boundaries.
  • A single logo renders as a one-tile row with nothing to scroll; the peek is of nothing, and the row is static.

Platform Primitives Used

  • theme-blocks — each logo is a block, so a merchandiser can add, reorder, relabel and remove them without a developer.
  • liquid-schema-settings — three block settings, a background color, a heading and split padding ranges; the six-logo preset is what makes the section usable on first placement.
  • css-custom-properties--columns drives the grid's column sizing per breakpoint and --logo-strip-bg carries the color setting, so one stylesheet serves every instance.
  • responsive-imagesimage_tag with a four-step srcset and a sizes expression sized to the tile, not the viewport.

Where It Runs

The home page, as one instance on the index template. The section has no template restriction; it happens to be placed once, where press and award logos sit below the hero.

What This Demonstrates

  • Media and asset loading strategy — the primary capability: a loading decision made by not loading. No slider script, no library, a srcset sized to the tile. Described as technique, and not offered as a measured result.
  • Design system and reusable component library — a component whose per-instance variation is entirely custom-property values over a fixed stylesheet.
  • Testimonials, press and social proof modules — the merchandising job: press and award logos as a trust row, authored in the editor.

How We Know

One section file from the theme, roughly 280 lines of Liquid, schema and stylesheet, plus one client planning record. That record — the client's inventory of features the redesign had to carry over from the previous site — lists a press ticker. This strip is the most likely home for it, and no record names the section or describes its construction. The scroll-snap approach, the peek and the name fallback are code-side decisions, read from the section. The client's record explains that a press row had to exist; the code explains how it was built without a script.

Related Builds

The Buy-vs-Build Question

A speed app works from outside the theme, rewriting what the platform served. The decision in this section was made inside it: a row of logos doesn't need a carousel library, so none is loaded. That's a statement about where the decision lives, not about what it achieved — no measurement is attached to this build, and none is claimed. Where that kind of decision belongs is the subject of the site speed optimization apps decision page.

Provenance & Evidence

  • Client: LUS Brands — loveurcurls.com
  • Surface: Home page
  • Templates served: one — index
  • Complexity: Low
  • Attribution: Deploi-authored. The section, its block schema, its grid and its stylesheet are ours. It runs in a theme built on Dawn 15.4.1 and depends on nothing in Dawn beyond the layout that mounts it.
  • Status: Live, verified 2026-09-07
  • Evidence: One section file from the theme, plus one client planning record
  • Confidence: Strong — the client record establishes that a press row had to carry over; the code shows the construction. No performance measurement exists for this build and none is claimed.
  • Primary capability: Media and asset loading strategy

Ready for a Logo Row Without a Library?

You dream it. We build it. If your homepage loads a slider to show six static logos, Contact us today and we'll build the row in CSS — and if you want a number out of it, let's talk about measuring first.

More builds