Shopify Builds>LUS Brands>Audience-targeted announcement bar with sticky-height measurement

Audience-Targeted Announcement Bar With Sticky-Height Measurement

Dawn's announcement bar, extended so each announcement block carries an audience setting, the section counts the matching blocks before it renders, and that count decides whether the page gets no bar, a static line or a carousel — with slide numbering that matches what's actually on screen, and a pinned height the rest of the layout can read.

Shopify renders one header group for the whole storefront, so a store running two sub-brands shows every announcement to everyone. This Dawn announcement bar gives each block an audience setting, counts the matching blocks in Liquid before render, and picks one of three DOM shapes from that count — nothing, a static line, or a carousel numbered from the visible set. When pinned, it publishes its measured height as a CSS custom property.

The Problem

A Shopify theme has one header group. It renders on every template, and there is no per-template announcement bar and no audience segmentation to hang one on. For a storefront that sells an adult line and a kids line from the same theme, that means every promotion runs everywhere: a kids offer on an adult product page, an adult offer on the kids landing page, both in the same rotating bar.

There's a second, quieter problem with a bar that pins to the top. Once it's position: sticky, the sticky header below it and any section that offsets from the top of the viewport need to know how tall it is — and its height isn't a constant. It wraps to two lines on a narrow screen, it changes when the copy changes, and it's zero when there's nothing to show.

The Constraint

The filtering had to happen in Liquid, before the response left Shopify. Hiding the wrong announcements with JavaScript after paint means a shopper sees the kids promo for a beat before it disappears, and a screen reader may already have announced it. Hiding them with CSS leaves them in the DOM, where a carousel still counts them as slides and a screen reader still reads them.

That second point is the accessibility constraint. Dawn's slideshow numbers its slides — ids, aria-label text of the form "n of m", aria-hidden on the inactive ones — from the loop index. If three blocks exist and one is filtered out, a loop-index scheme announces "3 of 3" for a bar that shows two. The numbering had to be rebuilt from the visible set.

And the section is Dawn's. The header group, the slideshow component and the section's own scaffolding are the vendor's, and stayed so.

What We Built

announcement-bar.liquid is Dawn's section with an audience select — all, adults or kids — added to each announcement block. Before rendering anything, the section counts only the blocks whose audience applies to the current page. That count drives three separate render paths.

Zero visible blocks. The section is rendered with display: none and the sticky-height custom property is set to 0px, so nothing downstream keeps an offset for a bar that isn't there.

One visible block. A single static announcement — no carousel markup, no slider script, no navigation. The block renders as text, with or without a link.

Two or more. Dawn's slideshow-component carousel, with one change to how it's numbered: slide ids, the "n of m" aria-label text and aria-hidden are all driven by a separate visible_slide_index that increments only for rendered blocks. What a screen reader counts matches what's on screen.

Sticky height. An enable_sticky_announcement setting makes the section position: sticky and installs a small inline script that measures the bar's offsetHeight and publishes it to the document root as --announcement-sticky-height. It re-measures on resize, on load, through a ResizeObserver, and once more across a double requestAnimationFrame after first paint, so the value is right after fonts load and after the bar wraps. Anything else in the layout that needs to sit below the bar reads that one property.

The utility bar around it. The announcements share their row with a social-icon strip and the storefront's country and language forms, each behind its own toggle. Grid modifier classes switch the row between two and three columns depending on which of the three are present, so a bar with no social icons doesn't leave an empty third of its width.

LIQUID · BEFORE RENDERBROWSER · WHEN STICKY Announcement blocks audience: all / adults / kids up to Dawn's block limit Count visible blocks only blocks that apply to this page 0 → hidden display:none · height 0px 1 → static line no carousel markup, no slider script 2+ → Dawn carousel ids · "n of m" · aria-hidden from visible_slide_index Measure offsetHeight resize · load · ResizeObserver · 2×rAF sticky header and offset sections read one value instead of guessing Publish on :root --announcement-sticky-height filtered in Liquid count when sticky writes read by 0 also writes height 0px
The count is taken before anything renders, and everything downstream follows from it: which DOM shape exists, how the slides are numbered, and whether the height property is measured or zeroed. Hidden announcements are never in the document, so the carousel, the screen reader and the layout all agree on what is there.

Why This Way

Filtering in Liquid rather than in CSS or JavaScript is the whole accessibility argument. An announcement that doesn't apply is never in the DOM, so the carousel's slide count, the "n of m" labels a screen reader announces and the visible bar are the same set. There is no moment where the page contains a kids promo an adult shopper can't see but a screen reader can.

Publishing the height as a custom property instead of hard-coding it lets the sticky header and any offset-dependent section read one value that stays correct when the bar wraps to two lines on a phone, and read 0px when there's no bar at all. The number is measured, not assumed, and it's measured again whenever it could have changed.

Two costs come with this. The section now has three render paths where Dawn had one, and a change to the bar's markup has to be made in each. And the height contract is only as good as its subscribers: a section that positions itself from the top of the viewport without reading --announcement-sticky-height gets the same stale offset it always would have. The property is a promise the layout keeps only where it is read.

Why Not an App

Announcement-bar apps with audience rules exist and are a common purchase, and for a store whose targeting depends on geography, customer tags or schedules, one earns its place. The mechanism is a script that decides after load what to show.

Here, which announcements apply is settled in Liquid, from a setting on each block, before the HTML leaves Shopify — the same render in which the header decides which menu to show. That puts the right announcements in the initial document and keeps the slide numbering honest. An app would have added a script above the fold to make a decision the page had already made.

Implementation Notes

  • Dawn's component-slider.min.css is included as a blocking stylesheet only when two or more announcements are visible; with one, it's deferred through the media="print" onload swap, since no slider will render.
  • Under request.design_mode, the section loads Dawn's theme-editor.min.js and a visibility fix, so the editor's block-select preview doesn't show every slide stacked at once.
  • Country and language localization forms render into the same utility bar behind their own toggles, guarded by localization.available_countries.size > 1, so a single-market shop never sees an empty selector.
  • The zero-visible path still writes --announcement-sticky-height: 0px, so a downstream offset computed for a bar that has since been filtered away doesn't go stale.
  • Grid modifier classes switch the utility bar between two and three columns depending on which of social icons, announcements and localization are present.
  • Slide ids, the "n of m" aria-label text and aria-hidden are all numbered from visible_slide_index, not the block loop's forloop.index.

Edge Cases

  • Every block filtered out for the current audience: the section is hidden and the sticky height is zeroed, so the header sits where it would with no bar.
  • Exactly one visible announcement: static markup, no carousel, and the slider stylesheet deferred rather than blocking.
  • An announcement without a link renders without an anchor wrapper and without the arrow icon, so there is no empty link for a keyboard user to land on.
  • The social-icon row only renders when at least one of the nine social settings is filled.
  • Localization selectors are suppressed when the shop has a single country or a single language, so the utility bar never shows a selector with one option.

Platform Primitives Used

  • theme-blocks — each announcement is a block; the audience select is a block setting, and the count that drives everything is a count of blocks.
  • liquid-schema-settings — the sticky toggle, the social settings and the localization toggles are section settings on Dawn's schema.
  • shopify-markets — the country and language forms in the utility bar are the localization forms Markets provides, rendered here behind a size guard.
  • css-custom-properties--announcement-sticky-height is the contract between the bar and everything positioned beneath it.

Where It Runs

Globally, as a section in the storefront's header group, which Shopify renders on every template. There is no per-template placement — which is the problem the audience setting exists to solve — and one instance serves the whole storefront.

What This Demonstrates

  • Accessibility remediation at component level — the primary capability: slide ids and "n of m" labels numbered from what's actually rendered, and announcements that don't apply kept out of the document rather than hidden in it.
  • Announcement bars and promotional messaging — several concurrent messages per audience, in one bar, without a second header group.
  • Multi-brand and sub-brand theming — one storefront serving two audiences, where the announcement layer follows the same split as the header.

How We Know

One Dawn section and one icon snippet read from the theme, roughly 650 lines together, plus two client working-session records. The records establish the two-audience shape of the storefront — an adult line and a kids line served from one theme — which is the reason an audience setting exists on an announcement block at all. Neither record discusses the announcement bar itself. The three render paths, the visible-slide numbering and the height contract are code-side decisions, reconstructed from the section; the client's record explains why the split mattered, and the code explains what it did to the bar.

Related Builds

The Buy-vs-Build Question

An app buys targeting rules and pays for them with a bar decided after the page has loaded. Extending Dawn's section bought announcements filtered before the HTML leaves Shopify, slide numbering that matches the screen and a height the layout can read — and cost three render paths in place of one. Whether that trade holds depends on how much of your targeting is a question the theme can already answer, which is the subject of the accessibility remediation decision page.

Provenance & Evidence

  • Client: LUS Brands — loveurcurls.com
  • Surface: Global — the storefront's header group
  • Templates served: one section group, header-group, rendered on every template
  • Complexity: Medium
  • Attribution: Built on top of Dawn 15.4.1; this section is our modification. The header group, the slideshow component and the section scaffolding are Dawn's. The audience setting, the visible-block count and its three render paths, the visible-slide numbering and the sticky-height script are ours.
  • Status: Live, verified 2026-09-07
  • Evidence: One section and one snippet from the theme, plus two client working-session records
  • Confidence: Strong for the audience split, which the client record establishes; the bar's own mechanics are documented from the code, with no record discussing the section
  • Primary capability: Accessibility remediation at component level

Ready for an Announcement Bar That Knows Who It's Talking To?

You dream it. We build it. If your promotions run everywhere because your theme has one header, Contact us today and we'll give each announcement an audience — in the HTML, before the page is sent.

More builds