---
title: Scroll to Top
description: Floating scroll-to-top button
visibility: guest
draft: false
---

# Scroll to Top

The `<x-wirekit::scroll-to-top>` component renders a fixed-position button that appears after the user scrolls past a configurable viewport depth. Clicking it smooth-scrolls back to the top of the page.

## Basic Usage

:::preview{title="Scroll to Top Button"}
<x-wirekit::row gap="lg" align="center">
    <x-wirekit::scroll-to-top size="sm" :force-visible="true" style="position: static; display: inline-flex;" />
    <x-wirekit::scroll-to-top size="md" :force-visible="true" style="position: static; display: inline-flex;" />
    <x-wirekit::scroll-to-top size="lg" :force-visible="true" style="position: static; display: inline-flex;" />
</x-wirekit::row>
:::

```blade
{{-- Place once in your layout, typically at the end of <body> --}}
<x-wirekit::scroll-to-top />
```

The button is invisible by default and fades in once the user scrolls past **1.5x the viewport height**. It positions itself in the **bottom-right** corner.

## Custom Threshold

The `threshold` prop controls when the button appears, expressed as a multiplier of the viewport height:

```blade
{{-- Appear early (after half a screen of scrolling) --}}
<x-wirekit::scroll-to-top :threshold="0.5" />

{{-- Appear late (after 3 full screens) --}}
<x-wirekit::scroll-to-top :threshold="3" />

{{-- Default: 1.5x viewport height --}}
<x-wirekit::scroll-to-top />
```

| Value | Meaning |
| --- | --- |
| `0.5` | After scrolling half the viewport |
| `1` | After scrolling one full viewport |
| `1.5` | After scrolling 1.5 viewports (default) |
| `3` | After scrolling 3 viewports |

## Size Variants

```blade
<x-wirekit::scroll-to-top size="sm" />  {{-- Compact --}}
<x-wirekit::scroll-to-top size="md" />  {{-- Default --}}
<x-wirekit::scroll-to-top size="lg" />  {{-- Large, touch-friendly --}}
```

## Position

The button defaults to the bottom-right corner. Use the `position` prop to move it:

```blade
<x-wirekit::scroll-to-top position="bottom-right" />  {{-- Default --}}
<x-wirekit::scroll-to-top position="bottom-left" />
<x-wirekit::scroll-to-top position="top-right" />
<x-wirekit::scroll-to-top position="top-left" />
```

The offset from the edge uses the `--padding-wk-x-lg` design token, so it adapts to your theme.

## How It Works

1. An Alpine.js `scroll` event listener (with `{ passive: true }`) tracks the scroll position
2. `requestAnimationFrame` throttles updates to avoid layout thrashing
3. When `window.scrollY > window.innerHeight * threshold`, the button fades in via `x-transition`
4. Clicking calls `window.scrollTo({ top: 0, behavior: 'smooth' })` for native smooth scrolling
5. The `destroy()` lifecycle hook removes the scroll listener on component teardown (e.g. Livewire navigation)

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `threshold` | `float` | `1.5` | Viewport multiplier — button appears after scrolling `threshold * viewportHeight` pixels |
| `size` | `string` | `'md'` | `'sm'`, `'md'`, `'lg'` |
| `position` | `string` | `'bottom-right'` | `'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'` |
| `scope` | `string\|null` | `null` | Scoped personalization key |

## Accessibility

- `<button type="button">` — semantically correct, keyboard-focusable
- `aria-label="Scroll to top"` — announces the action to screen readers
- Visible focus ring via `focus-visible:ring-*` design tokens
- SVG icon is `aria-hidden="true"` — purely decorative
- Smooth scrolling respects `prefers-reduced-motion: reduce` (browsers disable `behavior: 'smooth'` automatically)
- `x-cloak` prevents flash of the button before Alpine initializes

## Keyboard Interaction

| Key | Action |
|-----|--------|
| `Tab` | Move focus to the button |
| `Enter` / `Space` | Activate the button |

## Pitfalls

- **Don't show scroll-to-top above the fold.** The component auto-shows after 200vh of scroll by default — overriding the threshold to "always visible" defeats the disclosure pattern.

## Design Tokens

| Token | Purpose |
| --- | --- |
| `--color-wk-accent` | Button background |
| `--color-wk-accent-fg` | Arrow icon color |
| `--color-wk-accent-hover` | Hover background |
| `--shadow-wk-lg` | Drop shadow for elevation |
| `--z-wk-sticky` | Z-index (above content, below overlays) |
| `--size-wk-sm` / `md` / `lg` | Button dimensions per size |
| `--padding-wk-x-lg` | Offset from viewport edge |
| `--ring-wk-width` | Focus ring width |
| `--transition-wk-duration` | Hover/show animation speed |

## Customization

Override defaults in `config/wirekit.php`:

```php
'components' => [
    'scroll-to-top' => ['size' => 'lg'],
],
```

## Personalization

Override classes globally:

```php
use Pushery\WireKit\WireKit;

WireKit::personalize('scroll-to-top', [
    'base' => 'fixed z-50 rounded-full bg-neutral-900 text-white shadow-xl cursor-pointer',
]);
```

## Further Reading

- [MDN: `window.scrollTo()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo) — the smooth scroll API
- [MDN: `scroll` event](https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event) — passive listener for performance
- [MDN: `requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame) — throttling pattern used
- [MDN: `prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) — browsers auto-disable smooth scrolling
