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.
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.cssis included as a blocking stylesheet only when two or more announcements are visible; with one, it's deferred through themedia="print"onloadswap, since no slider will render. - Under
request.design_mode, the section loads Dawn'stheme-editor.min.jsand 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-labeltext andaria-hiddenare all numbered fromvisible_slide_index, not the block loop'sforloop.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-heightis 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
- Adults/Kids audience-switching header with automatic menu swap — the header that shares this section's header group and answers the same audience question; that page covers the switch, this one covers the bar.
- Rotating announcement carousel above the header, with pause control — a different storefront's announcement bar, authored from scratch in the layout rather than extended from Dawn's section, where the height is reserved in CSS instead of measured.
- Country/market selector hardened for three simultaneous instances — the localization form this utility bar renders is one of that build's three instances.
- Mobile sticky add-to-cart bar mirrored from the real product form — a different storefront's sticky bar at the other end of the viewport.
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.