Shopify Builds>LUS Brands>Arched-label icon grid PDP block

Arched-Label Icon Grid PDP Block

A six-slot row of circular badge icons with captions that curve over them — an SVG textPath per label, an arc id composed from block.id so the block can repeat, and layout driven entirely by custom properties the merchandiser sets.

CSS cannot bend a caption over a circular icon; only an SVG textPath can, and a textPath needs a path id that stays unique when the same block renders twice on one page. This product-page block derives each arc id from block.id, hides the decorative SVG from assistive tech while guaranteeing every icon an accessible name, and drives columns and icon size through CSS custom properties written from block settings.

The Problem

Product claims on a haircare PDP — sulfate-free, curl-safe, kid-friendly — are sold as a row of round badge icons with a short label curved above each disc. That's a brand-visual decision, and the platform has nothing that renders it. Shopify's theme editor offers image and text settings; the theme's job is to turn them into markup a browser can draw. Curved text is the sticking point. CSS has no property that bends a run of characters along an arc; the only way to do it in HTML is an SVG <textPath> referencing a <path> by id.

That reference is where a theme block goes wrong. A block is designed to be repeatable — a merchandiser can add two icon grids to one product page — and an SVG path id is a document-wide id. Two blocks that share a path id give the second caption the first block's arc, silently.

The Constraint

Everything the block needs has to come from the theme editor, because the badges vary per product and the content team owns them. The section this block lives in has no repeatable sub-blocks, so a "list of icons" cannot be modeled as nested blocks; the six slots have to be six explicit setting pairs — image_1 through image_6, label_1 through label_6 — and the Liquid has to unroll them.

Sizing has to be adjustable without a developer too. Column counts differ between mobile and desktop, icon size differs with them, and the caption has to scale with the disc it sits over or the arc stops fitting. None of that could be a per-block CSS rule someone edits by hand.

And the caption, once it's inside an SVG, is invisible to a screen reader in any useful way. The icon still needs a name from somewhere.

What We Built

de_simple_icon_block.liquid renders up to six image-and-label slots. A {% for i in (1..6) %} loop resolves each slot through a case statement that maps the index to the matching image_N and label_N settings, so the unrolled settings read like a list even though the schema can't express one. A slot with no image is skipped, and the grid collapses to however many icons are configured.

Each label becomes an <svg viewBox="0 0 120 34">. Inside it, a <defs><path id="if-si-arc-{{ block.id }}-{{ i }}"> draws the arc, and a <textPath startOffset="50%" text-anchor="middle"> sets the caption along it, centered. Composing the id from block.id and the slot index is what makes the block safe to repeat: two instances on one page produce two sets of arc ids, and each caption follows its own path.

Layout and sizing come from CSS custom properties written inline from the block's settings — --if-cols-desktop, --if-cols-mobile, --if-icon-desktop, --if-icon-mobile and an accent color. The rules that consume those properties are fixed; the values are per block. Caption font-size uses clamp() tied to the icon size, so a larger disc gets a proportionally larger arc of text rather than the same caption on a bigger circle.

The SVG is aria-hidden and focusable="false" — it is decoration, and its text would be announced as a fragment out of context. The accessible name lives on the image instead, with a fallback chain: the label, then the image's own alt text, then a generic "Product badge icon" string, so no icon ever ships without a name. Images render through image_tag with a 150–600 srcset and sizes="(max-width: 749px) 25vw, 120px".

Why This Way

Driving the grid from custom properties means one set of rules handles every configuration. The alternative — emitting a scoped rule per block for every column and size setting — multiplies CSS with each block placed and makes the theme editor's every change a stylesheet change.

Deriving the arc id from block.id is the decision the accessibility work turns on, and it costs nothing at authoring time: the merchandiser never sees an id. A loop index alone would have been unique within one block and wrong the moment a second block existed.

The trade we made was on the caption. Putting the text inside the SVG is the only way to bend it, and it takes the text out of the accessible tree, so the name has to be reconstructed on the image with a fallback chain. That chain is only as good as the label the merchandiser typed. A blank label with a blank image alt gets the generic string — accurate, but not descriptive — and there is no way to make the block refuse to render in that state without also refusing a legitimately unlabeled icon.

Implementation Notes

  • Rendered from de_main_product.liquid as the simple_icon_grid block type, enabled in all four product templates.
  • Slots with no image are skipped in the loop, so the grid collapses cleanly to however many icons are configured rather than leaving empty cells.
  • Icon images use image_tag with a 150–600 srcset and sizes="(max-width: 749px) 25vw, 120px", with intrinsic dimensions from the image object.
  • Alt text falls back label → image.alt → a generic "Product badge icon" string, so no icon ships without an accessible name.
  • The arc SVG is aria-hidden and focusable="false". The visible caption exists only inside the SVG, which is why the alt fallback matters.
  • clamp() ties caption font-size to the icon-size custom property, so the arc of text scales with the disc rather than being set separately.

Edge Cases

  • A block with an image but no label renders the disc alone, with no empty SVG caption element above it.
  • Column counts are set independently for mobile and desktop, defaulting to 2 and 4, so a six-icon row wraps to three rows on a phone and sits in two on a desktop.
  • Two instances of the block on one page produce distinct arc ids, so neither caption inherits the other's path.
  • A slot whose image has been removed in the editor drops out of the grid; the remaining slots close up.

Platform Primitives Used

  • theme-blocks — the grid is a block of the product section, so it can repeat, and so every generated id has to be composed from block.id.
  • liquid-schema-settings — six image settings, six label settings, two column counts, two icon sizes and an accent color; the case statement exists because the schema has no list type for them.
  • css-custom-properties — how the block's settings reach its layout rules without a per-block stylesheet.

Where It Runs

The product detail page, as the simple_icon_grid block type of de_main_product.liquid, which is enabled in all four product templates: the default product template, the 2026 redesign template, the kids redesign template and the waiting-list template. The build record also tags a landing-page surface for the snippet, where product-claim rows appear on campaign pages.

What This Demonstrates

  • Accessibility remediation at component level — the primary capability: ids that survive repetition, decoration hidden from assistive tech, and an accessible name guaranteed by a fallback chain rather than left to whoever filled in the settings.
  • Design system and reusable component library — a component whose every visual parameter is a custom property, reusable across templates without a variant of its stylesheet.
  • Trust badges and value-proposition rows — the merchandising job: product claims as a row of badges, authored per product.

How We Know

One Liquid snippet from the theme, roughly 190 lines, plus two client working-session records. The records document a move away from cartoon-style graphics toward an icon style for ingredient and quiz imagery during the redesign; this grid is part of that change of visual language. The textPath construction, the id scheme and the alt fallback are not described in either record — they are code-side decisions, read from the snippet. The client's record explains why the icons look the way they do. The code explains how the captions bend.

Related Builds

The Buy-vs-Build Question

Trust-badge apps render a row of icons from their own dashboard, injected after the page loads, with their own markup and whatever accessible names they choose. Building the block bought captions that bend, ids that stay unique and a name on every icon — and cost a settings schema that unrolls six slots by hand. Where remediation belongs in the theme and where a tool earns its place is the question on the accessibility remediation decision page.

Provenance & Evidence

  • Client: LUS Brands — loveurcurls.com
  • Surfaces: Product detail page; also mountable from a landing page
  • Templates served: four product templates
  • Complexity: Medium
  • Attribution: Deploi-authored. The snippet, the textPath construction, the id scheme and the custom-property layout are ours. It runs as a block of our product section in a theme built on Dawn 15.4.1.
  • Status: Live, verified 2026-09-07
  • Evidence: One Liquid snippet from the theme, plus two client working-session records
  • Confidence: Strong — the client record establishes the visual direction; the code shows the construction
  • Primary capability: Accessibility remediation at component level

Ready for Badges That Say What They Show?

You dream it. We build it. If your product claims are a row of icons nobody can read with a screen reader — or two rows that share an id — Contact us today and we'll build the block so it repeats safely and names every icon.

More builds