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.
<l-sticky-bar>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.
The top phone uses
style="--offset: var(--header-height)"to dock under the in-page sticky header —--header-heightis defined once at:rootand shared between the header'sheightand the bar's offset, so they stay in sync.
Code
<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 instantWCAG2.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
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
placementAttributebottom(default) ortop. Edge to dock againstrootAttribute- 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 whenplacement="top"
Top layer. The bar uses
popover="manual"internally, so it paints in the document's top layer —z-indexis not needed and would be ignored. Targetl-sticky-bar:popover-opento style the revealed state.