Skip to content

Affix

WxAffix keeps something in view as its container scrolls past — a toolbar over a long table, a summary beside a long form. WxBacktop is the button that takes you back up.

Scroll the box: the toolbar sticks, and says so
14 orders
Order WX-4101
Order WX-4102
Order WX-4103
Order WX-4104
Order WX-4105
Order WX-4106
Order WX-4107
Order WX-4108
Order WX-4109
Order WX-4110
Order WX-4111
Order WX-4112
Order WX-4113
Order WX-4114
Stuck: false

Usage

vue
<template>
  <wx-affix :offset="0">
    <div class="toolbar">…</div>
  </wx-affix>
</template>

Sticky does the sticking

The only JavaScript here reports what happened; position: sticky does the work.

That is the whole design. Every version of this component that measures scroll offsets and switches to position: fixed inherits the same two bugs: the page jumps by the height of the element the moment it leaves the flow, and it sticks to the window rather than to whatever is actually scrolling — which in an admin is the main column, not the page. Sticky has neither, and it needs no scroll listener at all.

Knowing when it stuck

A sticky element cannot be styled differently while it is stuck — CSS has no selector for it. So a one-pixel sentinel sits where the element comes to rest, and an IntersectionObserver watches it: the moment it leaves the viewport, the element has stuck.

vue
<wx-affix @change="stuck = $event">
  <template #default="{ stuck }">
    <div class="toolbar" :class="{ 'is-stuck': stuck }">…</div>
  </template>
</wx-affix>

A shadow that appears only once it is stuck is what tells a reader the toolbar is floating over the rows rather than sitting on top of them.

Backtop

vue
<wx-backtop target="#main" :visibility-height="200" />

Name the target in an admin. The page there rarely scrolls — the main column does — so a button watching the window would appear when nothing has happened and do nothing when pressed.

Affix

PropTypeDefaultDescription
offsetnumber0How far from the edge it rests
position'top' | 'bottom''top'Which edge it sticks to
disabledbooleanfalsePuts it back in the flow
zIndexnumberLayer it sits on once stuck

Events: change (stuck: boolean) — reported once per change, not per scroll.

Slots: default — with { stuck }.

Backtop

PropTypeDefaultDescription
targetstring | HTMLElement | nullthe windowWhat scrolls
visibilityHeightnumber200How far down before it appears
rightnumber24Distance from the trailing edge
bottomnumber24Distance from the bottom
smoothbooleantrueGlides rather than jumps
ariaLabelstring'Back to top'Name of the button

Events: click (MouseEvent).

Slots: default — replaces the arrow.

Accessibility

An affixed element keeps its place in the document, so reading order and focus order are untouched — which is the other thing a fixed switch gets wrong. Backtop is a real button with a name, and reduced motion turns the glide into a jump.

Released under the MIT License.