Skip to content

Sticky Bar <l-sticky-bar>

A bar docked to the viewport edge, painted in the document's top layer. Pass for="<id>" to track an element; the bar reveals when that element scrolls out of view (e.g. an Add to cart button on a mobile product page). Omit for for a permanently visible bar.

Common use cases: mobile product Add to cart, sticky save action on long forms, post-form newsletter signup, cookie banners, environment indicators, promo announcements.

HTML tag<l-sticky-bar>
Native HTML
Progressive
Custom
Shadow DOM
Custom Element · Shadow DOM

Examples

Mobile product page

The canonical use: an Add to cart CTA stays reachable while the customer scrolls product details. Each iframe below is its own document — the sticky bar paints in its top layer and IntersectionObserver resolves against the iframe's viewport, so production behavior is faithfully simulated.

Both demos start with the bar revealed (the Add to cart button sits below the fold). Scroll inside a phone to bring the button into view — the bar hides. Keep scrolling past the button — the bar reveals again.

placement="bottom"
placement="top"

The top phone uses style="--offset: var(--header-height)" to dock under the in-page sticky header — --header-height is defined once at :root and shared between the header's height and the bar's offset, so they stay in sync.

Code
html
<button
  id="add-to-cart"
  class="l-button"
  data-variant="primary"
>
  Add to cart — €42
</button>

<l-sticky-bar for="add-to-cart">
  <div
    class="flex items-center justify-between gap-3 border-t border-[var(--l-color-divider)] bg-white px-4 py-3 dark:bg-zinc-900"
  >
    <span class="text-sm font-semibold text-primary">Magic Mouse — €42</span>
    <button
      class="l-button"
      data-variant="primary"
      data-size="sm"
    >
      Add to cart
    </button>
  </div>
</l-sticky-bar>

Accessibility

The element is a positioning shell — it adds no role of its own. Slotted content keeps its native semantics: a <button> stays a button, a <form> stays a form, links remain in the focus order.

Motion

Respects prefers-reduced-motion — the slide animation collapses to instant

WCAG2.3.3
Focus order

Slotted content stays in the natural focus order. Do not focus-trap inside the bar — it is not a dialog

WCAG2.4.3
Contrast

The bar inherits text and background from slotted content — apply your own contrast tokens

WCAG1.4.3

API reference

Importing

js
import 'luxen-ui/sticky-bar';

Attributes & Properties

forAttribute
HTML id of the element to track. The bar reveals when it leaves the viewport. Omit for a permanently visible bar
placementAttribute
bottom (default) or top. Edge to dock against
rootAttribute
HTML id of the scrolling ancestor used as the IntersectionObserver root. Omit to use the viewport. Useful for nested scroll containers

Events

showEvent
Fired before the bar reveals. Cancelable
after-showEvent
Fired after the reveal animation completes
hideEvent
Fired before the bar hides. Cancelable
after-hideEvent
Fired after the hide animation completes

Slots

(default)Slot
Bar content. Owns its own background, padding, and typography

CSS custom properties

--show-durationName
Reveal animation duration. Default 200ms
--hide-durationName
Dismiss animation duration. Default 200ms
--offsetName
Distance from the active edge. Default 0px. Use to clear a sticky header when placement="top"

Top layer. The bar uses popover="manual" internally, so it paints in the document's top layer — z-index is not needed and would be ignored. Target l-sticky-bar:popover-open to style the revealed state.