Scroll to Top

Free

Fixed scroll-progress ring overlay with circle/rounded/pill shapes, four premium appearances (solid, glass, gradient, outline) and a configurable show threshold.

</>sv-scroll-to-top scrollutilityoverlay
import { SvScrollToTopComponent } from '@styloviz/layout';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Pill with progress

example.htmlAngular
<sv-scroll-to-top [threshold]="300" variant="pill" position="bottom-right" [showProgress]="true" label="Top" />

Glass appearance

example.htmlAngular
<sv-scroll-to-top appearance="glass" variant="circle" position="bottom-right" />

Gradient pill

example.htmlAngular
<sv-scroll-to-top appearance="gradient" variant="pill" label="Top" />

Inside a scrollable panel

example.htmlAngular
<div class="relative h-80 overflow-hidden">
  <div #panel class="h-full overflow-y-auto"><!-- …content… --></div>
  <sv-scroll-to-top [scrollTarget]="panel" [contained]="true" [threshold]="120" />
</div>

Move focus to the page heading

Scroll AND move keyboard focus, so the next Tab starts at the top instead of from a button that just hid itself.

example.htmlAngular
<sv-scroll-to-top [focusTarget]="true" />
<sv-scroll-to-top focusTarget="#main-title" />

Sharing a corner with a FAB

Both join the shared floating layer: the dial keeps the corner, the button stacks above it.

example.htmlAngular
<sv-speed-dial [actions]="actions" floating="bottom-right" />
<sv-scroll-to-top position="bottom-right" />

When to use

  • Helping users return to the top of long, scroll-heavy pages.
  • Articles, feeds, documentation and dashboards with deep content.
  • Inside a scrollable panel, drawer or card via `contained` + `scrollTarget`.
  • For short pages it adds clutter — skip it.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
thresholdnumber200Scroll distance (px) before the button appears.
variantScrollToTopVariant'circle'Shape variant: circle, rounded or pill.
appearanceScrollToTopAppearance'solid'Premium surface style: solid, glass, gradient or outline.
positionScrollToTopPosition'bottom-right'Screen corner to anchor the button.
showProgressbooleantrueShow the scroll-progress ring around the button.
labelstring''Optional text label next to the icon.
ariaLabelstring'Scroll to top'Accessible label for the button.
demobooleanfalseDemo / preview mode: button is always visible regardless of scroll.
containedbooleanfalsePin the button inside its nearest positioned ancestor (`position: absolute`) instead of the viewport (`position: fixed`) — for scrollable panels, cards and drawers.
scrollTargetHTMLElement | nullnullExplicit scroll container to watch. When unset, the nearest scrollable ancestor (or the window) is used.
scrollBehaviorScrollToTopBehavior'smooth'Scroll animation. `'smooth'` is honoured only when the user has NOT asked for reduced motion — under `prefers-reduced-motion: reduce` the jump is instant. `'auto'` always jumps.
focusTargetboolean | stringfalseWhere keyboard focus goes after scrolling. `false` leaves focus alone when it is somewhere else (a mouse click in Safari never focuses the button) — but if the BUTTON holds focus as it is activated or hides (outside `demo`, where it never hides), focus is never stranded on it (it becomes `aria-hidden` and leaves the tab order): it moves as for `true`, else to `main` / `[role="main"]` (made `tabindex="-1"`), else it is released to the document. `true` moves it to the page's first heading — `main h1`, else the first `h1`, else the first heading of any level — inside the scroll root. A string is a CSS selector for the target. A target that is not focusable gets `tabindex="-1"`. Focus moves with `preventScroll`, so the scroll is not cut short.
floatingOrdernumberSV_FLOATING_ORDER.scrollToTopRank in the shared floating layer's corner stack; lower sits nearer the edge. The default yields the corner to a Speed Dial (10) and a host's launcher registered below 20. Only used in the fixed (viewport) mode.

Outputs

EventPayload typeDescription
clickedvoidEmitted when the button is clicked (page scrolls to top).

Customization & theming

  • Circle, rounded or pill shapes.
  • Four premium appearances: solid, glass, gradient, outline — composable with any shape.
  • Progress ring recolours itself to match the appearance.
  • Viewport-fixed or `contained` inside a scrollable panel/drawer.
  • Configurable show threshold.
  • Shows live scroll progress.
  • Customisable button name via `ariaLabel`.
  • Floating layer: in the viewport-fixed mode the button joins the shared floating layer while it is shown (`floatingOrder`, default 20 — above a Speed Dial at 10), so it stacks clear of a FAB, affixed blocks and any region the host registers (chat, cookie banner, bottom nav), plus the device safe-area inset. Hidden, contained and demo buttons register nothing.
  • `scrollBehavior`: `smooth` (default) or `auto`. Smooth is downgraded to an instant jump under `prefers-reduced-motion`, read at click time.
  • `focusTarget`: `true` focuses `main h1`, else the first `h1`, else the first heading, inside the scroll root; a string is a CSS selector. A non-focusable target gets `tabindex="-1"`; focus moves with `preventScroll` so the scroll is not cut short. An invalid selector warns in dev mode and leaves focus alone. Unset, focus moves only when the button itself holds it (see Accessibility).
  • Variant axes: `variant` (circle, rounded, pill) and `appearance` (solid, glass, gradient, outline) are appearance; `showProgress`, `contained`, `scrollBehavior` and `focusTarget` are behavioural modes. The progress ring draws only on circle and rounded — pill has no room for it. Every appearance survives forced colors with a ButtonText edge; glass and gradient lose their surface there by design.
  • Scroll roots and containing blocks (X-9): `scrollTarget` wins, else the nearest scrollable ancestor, else the window. The floating layer is viewport-level, so `contained` buttons do not register. The fixed button is not portalled: an ancestor with `transform`, `filter`, `perspective`, `backdrop-filter`, `contain` or `container-type` becomes its containing block, so it pins to that ancestor instead of the viewport — keep it outside animated or filtered wrappers.
  • Browser limits: safe-area insets only apply with `viewport-fit=cover`; smooth scrolling is ignored by browsers without `scroll-behavior` support (the jump is instant); iOS's virtual keyboard can cover a fixed button.

Best practices

  • Do: Set a `threshold` so the button only appears after meaningful scrolling.
  • Do: Keep it out of the tab order until visible (handled).
  • Tip: Treat `showProgress` as a visual hint only — the ring is decorative and has no text equivalent, so it is not a reading-progress indicator.
  • Do: Set `focusTarget` on long pages so keyboard and screen-reader users land at the top too, not only the viewport.
  • Tip: A host chat launcher or cookie banner in the same corner should register with `SvFloatingLayerService`; the button then stacks clear of it.
  • Tip: Sharing a corner with a Speed Dial (X-5): the button can only stack clear of a dial that registered. A default `<sv-speed-dial>` is host-positioned and registers nothing, so set its `floating` input to the shared corner — otherwise the two overlap.

Accessibility

  • Labelled button (`ariaLabel`) that scrolls the page to the top.
  • Disabled, aria-hidden and removed from the tab order until it becomes visible.
  • Focus after scrolling (X-10): with `focusTarget` unset, focus that is elsewhere is left alone (a mouse click in Safari never focuses the button). But if the button itself holds focus when it is activated or hides, focus is never stranded on a control that just became `aria-hidden`: it moves to the page's main heading (as `focusTarget: true`), else to `main` / `[role="main"]`, made `tabindex="-1"`, else it is released to the document. A `demo` button never hides, so it keeps focus. An explicit `focusTarget` always wins.
  • Forced colors (X-7): the focus ring is a box shadow, which forced colors removes, so every shape and appearance also draws a system-coloured focus outline in that mode.
  • Reduced motion: no smooth scroll, and the show/hide and ring transitions are stilled.
  • The progress ring is decorative (`aria-hidden`) and has no text equivalent — it is not a reading-progress indicator for assistive tech.

Feedback on Scroll to Top

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.