Skip to content
WANCRAFT

D.106Navigation & Menus

Scroll to Top

A back-to-top button with a scroll-progress ring that appears after a threshold, for the window or any scroll box.

NewReactMotionSVGTailwindbasic

Overview

Scroll to Top appears once the reader has scrolled past a threshold and disappears again at the top. A ring around the arrow fills with scroll progress (updated at most once per frame, without re-rendering), and the label tells screen-reader users how far down they are. Activating it scrolls smoothly back to the top (instantly with reduced motion) and then moves keyboard focus to the top of the content: a target you pass, the scroll container, or the page's main landmark. It watches the window by default, or any scrollable element through a container ref, and sits in any bottom corner.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/scroll-to-top.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/scroll-to-top.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/scroll-to-top.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ScrollToTop } from "@/components/ui/scroll-to-top";

export function ScrollToTopExample() {
  return (
    <ScrollToTop />
  );
}

03Props

PropTypeDefaultDescription
containerRefObject<HTMLElement | null>—Scrollable element to watch and scroll. Omit for the window (the button is then position: fixed).
thresholdnumber240Scroll distance in px after which the button appears.
position"bottom-right" | "bottom-left" | "bottom-center""bottom-right"Corner of the viewport, or of the nearest positioned ancestor when using container.
offsetnumber24Distance from the edges in px.
sizenumber48Button diameter in px.
showProgressbooleantrueRing that fills with scroll progress.
accentstring"#FF7448"Ring and focus ring colour.
focusOnTopbooleantrueAfter scrolling, move focus to focusTarget, else the container, else <main>.
focusTargetRefObject<HTMLElement | null>—Element that receives focus at the top (made focusable with tabindex=-1 if needed).
labelstring"Back to top"Accessible label; the scrolled percentage is appended.
...propsButtonHTMLAttributes—Forwarded to the <button>; call event.preventDefault() in onClick to cancel.

04Features

  • 01Appears after a scroll threshold and hides at the top, with a spring entrance
  • 02Circular ring shows scroll progress through a motion value, throttled to one update per frame
  • 03Smooth scroll to top; focus moves to the top once the scroll settles (scrollend with a fallback)
  • 04Window or any scroll container via a ref; re-measures when the content resizes
  • 05Three bottom positions, adjustable offset and size
motion · springmotion · scroll-linkedinput · scrollinput · clickinput · keyboard

05Accessibility

  • A native <button> whose label includes the scrolled percentage (in 5% steps), e.g. “Back to top, 40% scrolled”.
  • After scrolling, focus moves to the top target (focusTarget, the container or <main>), so the next Tab continues from the top.
  • Visible focus ring; the ring and arrow SVGs are aria-hidden.
  • With prefers-reduced-motion the scroll jumps instantly and the button only fades.

06Limitations

  • With container, render the button inside a position: relative wrapper around the scroll box (not inside the scrolling element), so it stays pinned.
  • Elements that receive focus without being focusable get tabindex="-1" added.