Sticky Promo Banner
A sticky promo banner is a fixed-position <aside> that renders at the top or bottom edge of the viewport with a dismiss button. It uses the complementary landmark role so screen readers can navigate to and from it, and it uses inline position: fixed styling so consumers can adopt it without writing CSS.
Use it for time-bound announcements, cookie/consent prompts that have been explicitly designed as promo banners, marketing campaigns, and feature launches.
Implementation Notes
- Uses an
<aside>element withrole="complementary"andaria-label labelis required — the aria-label that names the landmarkdismissLabelis required — the aria-label for the dismiss buttonopendefaults totrue; whenfalse, the root element renders with thehiddenattributepositionis exposed asdata-position("top" | "bottom", default "bottom") and applied via inlinestyle- Inline
style="position: fixed; left: 0; right: 0; top: 0;"(top variant) orstyle="position: fixed; left: 0; right: 0; bottom: 0;"(bottom variant) — this is the only documented inline style in the design system besides what each framework needs - The dismiss button renders only when
onDismiss/ondismissis provided - All visible text comes from
childrenfor i18n flexibility
Props
label: string (required) -- aria-label for the aside regionopen: boolean (default: true) -- whether the banner is visibleposition: "top" | "bottom" (default: "bottom") -- sticky edgedismissLabel: string (required) -- aria-label for the dismiss buttononDismiss/ondismiss: callback (optional) -- dismiss click handlerchildren: slot -- banner content...restProps: any additional HTML attributes
Usage
Bottom banner with dismiss handler:
<StickyPromoBanner
label="Beta program promotion"
dismissLabel="Dismiss banner"
ondismiss={handleDismiss}
>
<p>Try our new beta features. <a href="/beta">Learn more</a>.</p>
</StickyPromoBanner>
Top banner, hidden until open becomes true:
<StickyPromoBanner
label="Maintenance window"
position="top"
open={isOpen}
dismissLabel="Dismiss"
ondismiss={() => setIsOpen(false)}
>
<p>Scheduled maintenance Saturday 22:00–23:00 UTC.</p>
</StickyPromoBanner>
Keyboard Interactions
- Tab: Move focus into the banner content and the dismiss button
- Enter / Space on the dismiss button: Dismiss the banner (calls handler)
- Escape: Not handled by default — consumers may add an Escape handler if appropriate
ARIA
<aside role="complementary" aria-label={label}>for the landmark namedata-positionreflects "top" | "bottom"- Dismiss button:
<button type="button" aria-label={dismissLabel}> - The root element uses the native
hiddenattribute whenopenisfalse
When to Use
- Use for promotional announcements, beta invites, feature launches, and other time-bound messages.
- Use when the message is supplementary and dismissable (not safety-critical).
- Use at the top of the viewport for high-visibility announcements; at the bottom for less intrusive promos.
When Not to Use
- Do not use for safety-critical or blocking messages — use AlertDialog or Banner instead.
- Do not use for cookie consent unless your design explicitly treats consent as a promo (most regulators expect a more prominent surface).
- Do not use for navigation; use Header or NavigationMenu.
Headless
Renders an <aside> with the complementary landmark, an inline-styled fixed position, optional dismiss button, and any provided children. The fixed-position inline styles are the only styling the component applies; visual treatment, padding, animation, and z-index are the consumer's responsibility.
Styles
Consumer CSS targets the sticky-promo-banner class. Use the data-position="top" and data-position="bottom" attribute selectors to adjust appearance per edge. Style the inner sticky-promo-banner-content and sticky-promo-banner-dismiss classes for layout and the dismiss icon.
Testing
- Verify the component renders an
<aside>element with classsticky-promo-bannerandrole="complementary" - Verify
aria-labelis present and equals thelabelprop - Verify
data-positionreflects thepositionprop (default "bottom") - Verify inline
styleincludesposition: fixed; left: 0; right: 0;plustop: 0(top) orbottom: 0(bottom) - Verify the
hiddenattribute is applied whenopenisfalse - Verify the dismiss
<button>renders only whenonDismiss/ondismissis provided - Verify the dismiss button has
type="button"andaria-labelequal to thedismissLabelprop - Verify the dismiss handler fires on click and on Enter/Space
Advice
- Designers: Keep the message short — one sentence with one link. Reserve top placement for higher-impact announcements; default to bottom for promos.
- Developers: Always supply
labelanddismissLabel. Persist the user's dismissal in storage so the banner does not reappear on every page load.
Related components
announcement-banner— a banner highlighting important messages for all usersbanner— a prominent message bar across the top of a pagebanner-box— a banner box that is inside a banner component, using flexbox horizontalgovernment-banner— a banner identifying a website as belonging to a government, with an expandable details panelmedical-banner— a prominent message bar across the top of a page, with medical informationmedical-banner-box— a medical banner box that is inside a medical-banner component, using flexbox horizontal, with medical information
References
- WAI-ARIA
complementaryrole: https://www.w3.org/TR/wai-aria-1.2/#complementary - MDN
<aside>: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside - MDN
position: fixed: https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed_positioning - Mozilla Protocol Design System: https://protocol.mozilla.org/