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
Progressive
Plain
Shadow-DOM
Shadow-DOM Custom Element

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

forstringProperty
HTML id of the element to track. When that element leaves the viewport, the bar reveals. Omit for a permanently visible bar.
rootstringProperty
HTML id of the scrolling ancestor used as the IntersectionObserver root. Omit to use the viewport. Useful for nested scroll containers (CMS preview panes, modals).
placement'bottom' | 'top'default:'bottom'Property
Edge to dock against.

Events

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

Slots

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

CSS custom properties

--show-durationdefault:200msCustom property
Reveal animation duration.
--hide-durationdefault:200msCustom property
Dismiss animation duration.
--offsetdefault:0pxCustom property
Distance from the active edge. 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.