Back To Top

Appears once you have scrolled the nearest scrolling ancestor — which it finds itself — and it moves focus, not just the viewport.

src/components/ui/back-to-top.tsx

About

From the component's own source, where it was written.

A button that appears once you have scrolled, and takes you back up.

**It finds its own scroller.** An app that scrolls the document and an app whose page is a scrolling <main> beside a fixed rail are both normal, and the component cannot know which it is in. It walks up from itself to the first ancestor that actually scrolls and listens to that, so it works in either without being told. targetRef overrides the search.

**A scroll listener, not a sentinel.** An earlier version watched an absolutely positioned marker with an IntersectionObserver, which is cheaper per frame and quietly wrong: an absolute box resolves against the nearest *positioned* ancestor, so the marker sat at the top of whatever card the button happened to be rendered inside rather than at the top of the page — and a button placed at the foot of a document, which is where this button goes, never appeared at all. The listener here is passive and coalesced into one frame, and it only sets state when the answer changes.

**It honours prefers-reduced-motion.** Smooth-scrolling a long page is exactly the kind of large-field motion that triggers vestibular symptoms, so the behaviour drops to an instant jump when the user has asked for less motion. scroll-behavior: smooth in CSS with no media query is the common version of this bug.

**Focus follows the scroll.** Moving the viewport without moving focus leaves a keyboard user's position where it was, so the next Tab continues from the bottom of the page they just left — this focuses the target, which is what makes it a real skip control rather than a visual one.

Install

npx astralyx-ui add back-to-top

Usage

import { BackToTop } from '@/components/ui/back-to-top'

<BackToTop showAfter={400}>Top</BackToTop>

Composer

Change the props and the source below follows.

Props
tsx
<BackToTop showAfter={400}>Back to top</BackToTop>

Examples

The button itself

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

BackToTop

Extends Omit<ComponentProps<'button'>, 'children'>.

PropTypeDefaultDescription
showAfternumber400Pixels of scroll before it appears. 0 shows it straight away.
targetRefReact.RefObject<HTMLElement | null>—Name the scrolling element instead of letting it be found.
focusRefReact.RefObject<HTMLElement | null>—Focused after scrolling. Defaults to the first heading or the body.
labelstring'Back to top'—
childrenReactNode——
fixedbooleantrueFixed to the corner of the viewport.
visibilityscroll offset—A passive scroll listener, coalesced to one read per frame and setting state only when the answer changes. The obvious alternative — an IntersectionObserver on an absolutely positioned sentinel — is cheaper and wrong: the sentinel resolves against the nearest positioned ancestor, so a button rendered at the foot of a page measures the card it sits in and never appears.
tabIndex-1 while hidden—A focusable control nobody can see is a trap.

Behaviour

TopicDetailDescription
scrollerfound, not assumedWalks up to the first ancestor that actually scrolls, so it works in a document-scrolling app and in one whose page is a scrolling main beside a fixed rail. targetRef overrides the search.
prefers-reduced-motionhonouredSmooth-scrolling a long page is exactly the large-field motion that triggers vestibular symptoms, so it drops to an instant jump. scroll-behavior: smooth with no media query is the common bug.
focusfollows the scrollMoving the viewport without moving focus leaves the next Tab at the bottom of the page you just left.