ScrollToButton
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
A floating action button that appears when scrolling, allowing users to quickly navigate to different parts of the page. Perfect for long-form content, documentation sites, and single-page applications.
Interactive Examples
The examples below show multiple scroll buttons to demonstrate different features. In real applications, use only one scroll button per page to avoid confusion—multiple buttons can overwhelm users and create unclear navigation patterns.
Features
prefers-reduced-motion support When to Use
- Long articles or documentation pages
- Single-page applications with multiple sections
- Chat interfaces (scroll to latest message)
- Comment sections (jump to end)
- Tables of contents or navigation aids
- Content is short and fits on one screen
- The page already has persistent navigation
- Users need to focus on sequential reading without jumping
Examples
Live Preview
Default (Icon Only)
The most common use case - a circular button with just an icon that appears when scrolling down.
Paragraph 1: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 2: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 3: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 4: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 5: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 6: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 7: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 8: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 9: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 10: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 11: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 12: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 13: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 14: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 15: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 16: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 17: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 18: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 19: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Paragraph 20: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
With Visible Label
Show both icon and text label for better clarity. The button becomes pill-shaped automatically.
Low Scroll Threshold
Control when the button appears by adjusting scrollThreshold. This one appears after just 100px.
Scroll to Bottom
Navigate to the end of content. The arrow automatically points down based on the target.
Custom Icon with Slot
Replace the default arrow with any icon using Vue's slot system.
Different Sizes
Available sizes: x-sm, sm, md (default), lg, xl
Different Shapes
Available shapes: circle (default), square, rounded, rounded-square, capsule
Custom Styling with CSS Shadow Parts
Style internal parts without breaking encapsulation using CSS Shadow Parts.
View Vue Code
<template>
<section>
<h2>Default (Icon Only)</h2>
<p>The most common use case - a circular button with just an icon that appears when scrolling down.</p>
<div class="example-container">
<p
v-for="i in 20"
:key="i"
class="example-paragraph"
>
Paragraph {{ i }}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
<VueScrollToButton :style="{bottom: '100px'}" />
</div>
</section>
<section class="example-section">
<h2>With Visible Label</h2>
<p>Show both icon and text label for better clarity. The button becomes pill-shaped automatically.</p>
<VueScrollToButton
label="Back to Top"
:scrollThreshold="200"
:showLabel="true"
/>
</section>
<section class="example-section">
<h2>Low Scroll Threshold</h2>
<p>Control when the button appears by adjusting <code>scrollThreshold</code>. This one appears after just 100px.</p>
<VueScrollToButton
label="Quick Access"
:showLabel="true"
:scrollThreshold="100"
:style="{right: '180px'}"
shape="rounded"
/>
</section>
<section class="example-section">
<h2>Scroll to Bottom</h2>
<p>Navigate to the end of content. The arrow automatically points down based on the target.</p>
<VueScrollToButton
:style="{bottom: '160px'}"
label="Go to Bottom"
target="bottom"
:scrollThreshold="400"
/>
</section>
<section class="example-section">
<h2>Custom Icon with Slot</h2>
<p>Replace the default arrow with any icon using Vue's slot system.</p>
<VueScrollToButton
label="Launch to Top!"
:style="{bottom: '220px'}"
:scrollThreshold="600"
>
<template #icon>
<Rocket :size="20" />
</template>
</VueScrollToButton>
</section>
<section class="example-section">
<h2>Different Sizes</h2>
<p>Available sizes: <code>x-sm</code>, <code>sm</code>, <code>md</code> (default), <code>lg</code>, <code>xl</code></p>
<div class="inline-examples">
<div class="inline-example">
<VueScrollToButton
size="x-sm"
style="position: static;"
/>
<span>x-sm</span>
</div>
<div class="inline-example">
<VueScrollToButton
size="sm"
style="position: static;"
/>
<span>sm</span>
</div>
<div class="inline-example">
<VueScrollToButton
size="md"
style="position: static;"
/>
<span>md</span>
</div>
<div class="inline-example">
<VueScrollToButton
size="lg"
style="position: static;"
/>
<span>lg</span>
</div>
<div class="inline-example">
<VueScrollToButton
size="xl"
style="position: static;"
/>
<span>xl</span>
</div>
</div>
</section>
<section class="example-section">
<h2>Different Shapes</h2>
<p>Available shapes: <code>circle</code> (default), <code>square</code>, <code>rounded</code>, <code>rounded-square</code>, <code>capsule</code></p>
<div class="inline-examples">
<div class="inline-example">
<VueScrollToButton
shape="circle"
:style="{bottom: '280px'}"
:scrollThreshold="800"
/>
<span>circle</span>
</div>
<div class="inline-example">
<VueScrollToButton
shape="square"
:style="{bottom: '340px'}"
:scrollThreshold="1000"
/>
<span>square</span>
</div>
<div class="inline-example">
<VueScrollToButton
shape="rounded"
:style="{bottom: '400px'}"
:scrollThreshold="1200"
/>
<span>rounded</span>
</div>
<div class="inline-example">
<VueScrollToButton
shape="rounded-square"
:style="{bottom: '460px'}"
:scrollThreshold="1400"
/>
<span>rounded-square</span>
</div>
<div class="inline-example">
<VueScrollToButton
shape="capsule"
:showLabel="true"
label="Top"
:style="{bottom: '580px'}"
:scrollThreshold="1800"
/>
<span>capsule</span>
</div>
</div>
</section>
<section class="example-section">
<h2>Custom Styling with CSS Shadow Parts</h2>
<p>Style internal parts without breaking encapsulation using CSS Shadow Parts.</p>
<VueScrollToButton
class="custom-gradient"
label="Styled Button"
:style="{bottom: '520px'}"
:scrollThreshold="1600"
:showLabel="true"
/>
</section>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { VueScrollToButton } from "agnosticui-core/scroll-to-button/vue";
import { Rocket } from "lucide-vue-next";
export default defineComponent({
name: "ScrollToButtonExamples",
components: {
VueScrollToButton,
Rocket,
},
});
</script>
<style scoped>
.example-section {
margin-top: 3rem;
}
.example-section h3 {
margin-bottom: 0.5rem;
}
.example-section p {
margin-bottom: 1rem;
}
.example-container {
min-height: 120vh;
padding: 1.5rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
position: relative;
}
.example-paragraph {
margin-bottom: 1.5rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.inline-examples {
display: flex;
gap: 2rem;
align-items: flex-end;
flex-wrap: wrap;
padding: 2rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.inline-example {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.inline-example span {
font-size: 0.875rem;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-text-2);
}
</style>
<style>
/* Custom gradient styling example - must be global to target shadow parts */
ag-scroll-to-button.custom-gradient::part(ag-button) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
ag-scroll-to-button.custom-gradient::part(ag-button):hover {
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6);
transform: translateY(-2px);
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html, css } from 'lit';
import 'agnosticui-core/scroll-to-button';
export class ScrollToButtonLitExamples extends LitElement {
createRenderRoot() {
return this;
}
render() {
return html`
<style>
.lit-example-section {
margin-top: 3rem;
}
.lit-example-section h3 {
margin-bottom: 0.5rem;
}
.lit-example-section p {
margin-bottom: 1rem;
}
.lit-example-container {
min-height: 120vh;
padding: 1.5rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
position: relative;
}
.lit-example-paragraph {
margin-bottom: 1.5rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.lit-inline-examples {
display: flex;
gap: 2rem;
align-items: flex-end;
flex-wrap: wrap;
padding: 2rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.lit-inline-example {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.lit-inline-example span {
font-size: 0.875rem;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-text-2);
}
/* Custom gradient styling example */
ag-scroll-to-button.custom-gradient::part(ag-button) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
ag-scroll-to-button.custom-gradient::part(ag-button):hover {
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6);
transform: translateY(-2px);
}
</style>
<section>
<h2>Default (Icon Only)</h2>
<p>The most common use case - a circular button with just an icon that appears when scrolling down.</p>
<div class="lit-example-container">
${Array.from({ length: 20 }, (_, i) => html`
<p class="lit-example-paragraph">
Paragraph ${i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
`)}
<ag-scroll-to-button style="bottom: 100px"></ag-scroll-to-button>
</div>
</section>
<section class="lit-example-section">
<h2>With Visible Label</h2>
<p>Show both icon and text label for better clarity. The button becomes pill-shaped automatically.</p>
<ag-scroll-to-button
label="Back to Top"
scroll-threshold="200"
show-label
></ag-scroll-to-button>
</section>
<section class="lit-example-section">
<h2>Low Scroll Threshold</h2>
<p>Control when the button appears by adjusting <code>scroll-threshold</code>. This one appears after just 100px.</p>
<ag-scroll-to-button
label="Quick Access"
show-label
scroll-threshold="100"
style="right: 180px"
shape="rounded"
></ag-scroll-to-button>
</section>
<section class="lit-example-section">
<h2>Scroll to Bottom</h2>
<p>Navigate to the end of content. The arrow automatically points down based on the target.</p>
<ag-scroll-to-button
style="bottom: 160px"
label="Go to Bottom"
target="bottom"
scroll-threshold="400"
></ag-scroll-to-button>
</section>
<section class="lit-example-section">
<h2>Custom Icon with Slot</h2>
<p>Replace the default arrow with any icon using slots.</p>
<ag-scroll-to-button
label="Launch to Top!"
style="bottom: 220px"
scroll-threshold="600"
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"></path>
<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"></path>
<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"></path>
<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"></path>
</svg>
</ag-scroll-to-button>
</section>
<section class="lit-example-section">
<h2>Different Sizes</h2>
<p>Available sizes: <code>x-sm</code>, <code>sm</code>, <code>md</code> (default), <code>lg</code>, <code>xl</code></p>
<div class="lit-inline-examples">
<div class="lit-inline-example">
<ag-scroll-to-button size="x-sm" style="position: static"></ag-scroll-to-button>
<span>x-sm</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button size="sm" style="position: static"></ag-scroll-to-button>
<span>sm</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button size="md" style="position: static"></ag-scroll-to-button>
<span>md</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button size="lg" style="position: static"></ag-scroll-to-button>
<span>lg</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button size="xl" style="position: static"></ag-scroll-to-button>
<span>xl</span>
</div>
</div>
</section>
<section class="lit-example-section">
<h2>Different Shapes</h2>
<p>Available shapes: <code>circle</code> (default), <code>square</code>, <code>rounded</code>, <code>rounded-square</code>, <code>capsule</code></p>
<div class="lit-inline-examples">
<div class="lit-inline-example">
<ag-scroll-to-button
shape="circle"
style="bottom: 280px"
scroll-threshold="800"
></ag-scroll-to-button>
<span>circle</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button
shape="square"
style="bottom: 340px"
scroll-threshold="1000"
></ag-scroll-to-button>
<span>square</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button
shape="rounded"
style="bottom: 400px"
scroll-threshold="1200"
></ag-scroll-to-button>
<span>rounded</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button
shape="rounded-square"
style="bottom: 460px"
scroll-threshold="1400"
></ag-scroll-to-button>
<span>rounded-square</span>
</div>
<div class="lit-inline-example">
<ag-scroll-to-button
shape="capsule"
show-label
label="Top"
style="bottom: 580px"
scroll-threshold="1800"
></ag-scroll-to-button>
<span>capsule</span>
</div>
</div>
</section>
<section class="lit-example-section">
<h2>Custom Styling with CSS Shadow Parts</h2>
<p>Style internal parts without breaking encapsulation using CSS Shadow Parts.</p>
<ag-scroll-to-button
class="custom-gradient"
label="Styled Button"
style="bottom: 520px"
scroll-threshold="1600"
show-label
></ag-scroll-to-button>
</section>
`;
}
}
customElements.define('scroll-to-button-lit-examples', ScrollToButtonLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { ReactScrollToButton } from "agnosticui-core/scroll-to-button/react";
export default function ScrollToButtonReactExamples() {
return (
<section>
<style>{`
.react-example-section {
margin-top: 3rem;
}
.react-example-section h3 {
margin-bottom: 0.5rem;
}
.react-example-section p {
margin-bottom: 1rem;
}
.react-example-container {
min-height: 120vh;
padding: 1.5rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
position: relative;
}
.react-example-paragraph {
margin-bottom: 1.5rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.react-inline-examples {
display: flex;
gap: 2rem;
align-items: flex-end;
flex-wrap: wrap;
padding: 2rem;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.react-inline-example {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.react-inline-example span {
font-size: 0.875rem;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-text-2);
}
/* Custom gradient styling example */
ag-scroll-to-button.custom-gradient::part(ag-button) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
ag-scroll-to-button.custom-gradient::part(ag-button):hover {
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6);
transform: translateY(-2px);
}
`}</style>
<h2>Default (Icon Only)</h2>
<p>The most common use case - a circular button with just an icon that appears when scrolling down.</p>
<div className="react-example-container">
{Array.from({ length: 20 }).map((_, i) => (
<p key={i} className="react-example-paragraph">
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
))}
<ReactScrollToButton style={{ bottom: "100px" }} />
</div>
<section className="react-example-section">
<h2>With Visible Label</h2>
<p>Show both icon and text label for better clarity. The button becomes pill-shaped automatically.</p>
<ReactScrollToButton
label="Back to Top"
scrollThreshold={200}
showLabel={true}
/>
</section>
<section className="react-example-section">
<h2>Low Scroll Threshold</h2>
<p>Control when the button appears by adjusting <code>scrollThreshold</code>. This one appears after just 100px.</p>
<ReactScrollToButton
label="Quick Access"
showLabel={true}
scrollThreshold={100}
style={{ right: "180px" }}
shape="rounded"
/>
</section>
<section className="react-example-section">
<h2>Scroll to Bottom</h2>
<p>Navigate to the end of content. The arrow automatically points down based on the target.</p>
<ReactScrollToButton
style={{ bottom: "160px" }}
label="Go to Bottom"
target="bottom"
scrollThreshold={400}
/>
</section>
<section className="react-example-section">
<h2>Custom Icon with Slot</h2>
<p>Replace the default arrow with any icon using slots.</p>
<ReactScrollToButton
label="Launch to Top!"
style={{ bottom: "220px" }}
scrollThreshold={600}
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"></path>
<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"></path>
<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"></path>
<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"></path>
</svg>
</ReactScrollToButton>
</section>
<section className="react-example-section">
<h2>Different Sizes</h2>
<p>Available sizes: <code>x-sm</code>, <code>sm</code>, <code>md</code> (default), <code>lg</code>, <code>xl</code></p>
<div className="react-inline-examples">
<div className="react-inline-example">
<ReactScrollToButton size="x-sm" style={{ position: "static" }} />
<span>x-sm</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton size="sm" style={{ position: "static" }} />
<span>sm</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton size="md" style={{ position: "static" }} />
<span>md</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton size="lg" style={{ position: "static" }} />
<span>lg</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton size="xl" style={{ position: "static" }} />
<span>xl</span>
</div>
</div>
</section>
<section className="react-example-section">
<h2>Different Shapes</h2>
<p>Available shapes: <code>circle</code> (default), <code>square</code>, <code>rounded</code>, <code>rounded-square</code>, <code>capsule</code></p>
<div className="react-inline-examples">
<div className="react-inline-example">
<ReactScrollToButton
shape="circle"
style={{ bottom: "280px" }}
scrollThreshold={800}
/>
<span>circle</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton
shape="square"
style={{ bottom: "340px" }}
scrollThreshold={1000}
/>
<span>square</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton
shape="rounded"
style={{ bottom: "400px" }}
scrollThreshold={1200}
/>
<span>rounded</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton
shape="rounded-square"
style={{ bottom: "460px" }}
scrollThreshold={1400}
/>
<span>rounded-square</span>
</div>
<div className="react-inline-example">
<ReactScrollToButton
shape="capsule"
showLabel={true}
label="Top"
style={{ bottom: "580px" }}
scrollThreshold={1800}
/>
<span>capsule</span>
</div>
</div>
</section>
<section className="react-example-section">
<h2>Custom Styling with CSS Shadow Parts</h2>
<p>Style internal parts without breaking encapsulation using CSS Shadow Parts.</p>
<ReactScrollToButton
className="custom-gradient"
label="Styled Button"
style={{ bottom: "520px" }}
scrollThreshold={1600}
showLabel={true}
/>
</section>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add ScrollToButtonThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | 'Back to Top' | Accessible label for the button (always used for aria-label) |
showLabel | boolean | false | Whether to display the label text alongside the icon |
icon | boolean | true | Show/hide the default icon. If false, label is shown as fallback |
scrollThreshold | number | 400 | Scroll position (in pixels) before the button becomes visible |
target | string | 'top' | Scroll target: 'top', 'bottom', or element ID/selector (e.g., 'section-3', '.my-section') |
direction | 'up' | 'down' | 'auto' | 'auto' | Arrow icon direction. 'auto' detects based on target |
smoothScroll | boolean | true | Enable smooth scrolling animation (respects prefers-reduced-motion) |
size | 'x-sm' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Button size |
shape | 'capsule' | 'rounded' | 'circle' | 'square' | 'rounded-square' | '' | '' | Button shape (empty string uses default) |
visible | boolean | false | Manually control visibility (typically managed internally by scroll position) |
Slots
| Slot | Description |
|---|---|
icon | Custom icon content. Overrides the default arrow icon when provided |
Vue Example:
<VueScrollToButton>
<template #icon>
<svg><!-- custom icon --></svg>
</template>
</VueScrollToButton>React Example:
<ReactScrollToButton>
<svg slot="icon">{/* custom icon */}</svg>
</ReactScrollToButton>CSS Shadow Parts
Style internal elements without breaking encapsulation:
| Part | Description |
|---|---|
ag-scrollto-button | The button wrapper element |
ag-button | The inner ag-button element (exported from nested component) |
ag-button-content | The flex container holding icon and label |
ag-icon-wrapper | The wrapper around the icon slot |
ag-label | The label text span element |
Example - Custom Gradient:
ag-scroll-to-button::part(ag-button) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}Example - Icon Positioning:
/* Icon on the right */
ag-scroll-to-button::part(ag-button-content) {
flex-direction: row-reverse;
}
/* Icon above label (vertical) */
ag-scroll-to-button::part(ag-button-content) {
flex-direction: column;
}Advanced Examples
Jump to Specific Section
<VueScrollToButton
label="Jump to Comments"
target="comments-section"
:scrollThreshold="300"
/>
<!-- Somewhere in your page -->
<section id="comments-section">
<!-- Comments content -->
</section>Chat Interface (Scroll to Bottom)
<VueScrollToButton
label="Latest Messages"
target="bottom"
direction="down"
:scrollThreshold="200"
/>Programmatic Target (Lit/React)
// Get reference to the component
const button = document.querySelector("ag-scroll-to-button");
const targetElement = document.querySelector(".my-dynamic-section");
// Set target programmatically
button.setTargetElement(targetElement);Accessibility
The ScrollToButton component follows accessibility best practices:
- ARIA Labels: Always includes
aria-labelwith thelabelprop value for screen readers - Keyboard Navigation: Fully keyboard accessible with standard button interactions
- Focus Management: Visible focus indicators for keyboard users
- Motion Sensitivity: Respects
prefers-reduced-motionsetting - disables smooth scroll when motion is reduced - Touch Targets: Minimum 44×44px touch target size for mobile accessibility
- Semantic HTML: Uses proper
role="button"and button semantics - Screen Reader Friendly: Hidden decorative icons with
aria-hidden="true"
Best Practices
Always provide descriptive labels: Even if not showing the label visually, the
labelprop is used foraria-labelvue<VueScrollToButton label="Return to page top" />Consider showing labels: For users with cognitive disabilities, visible text labels are clearer than icon-only buttons
vue<VueScrollToButton label="Back to Top" :showLabel="true" />Don't rely solely on color: If using custom styling, ensure sufficient contrast
cssag-scroll-to-button::part(ag-button) { /* Ensure 4.5:1 contrast ratio minimum */ background: #0066cc; color: #ffffff; }Test with keyboard: Ensure the button is reachable and activatable with Tab and Enter/Space keys
Position consistently: Keep the button in the same location across pages to build user familiarity
Design Tokens
The component uses AgnosticUI design tokens for consistency:
:host {
/* Spacing */
--ag-space-8: 2rem; /* Default position offset */
--ag-space-2: 0.5rem; /* Icon/label gap */
/* Motion */
--ag-motion-slow: 0.4s; /* Fade in/out duration */
/* Z-index */
--ag-z-index-sticky: 900; /* Stacking context */
}Override these in your global CSS or via CSS custom properties:
ag-scroll-to-button {
--ag-motion-slow: 0.2s; /* Faster transitions */
}Troubleshooting
Button doesn't appear:
- Check that page content is tall enough to exceed
scrollThreshold - Verify the component is not hidden by CSS
- Ensure z-index is high enough (
--ag-z-index-sticky: 900by default)
Smooth scroll doesn't work:
- Check browser support for
scroll-behavior: smooth - Verify
smoothScrollprop istrue - Check if user has
prefers-reduced-motionenabled
Custom icon not showing:
- Ensure slot content has the
slot="icon"attribute (Lit/React) or uses<template #icon>(Vue) - Verify the icon has appropriate size styling
Button appears too early/late:
- Adjust the
scrollThresholdprop (in pixels) - Default is
400, try200for earlier or600for later