A hook for responding to scroll position changes with callbacks for scrolling down and returning to top.
Usage
import { useScrollCallback } from "@servicetitan/ext-atlas";
function ScrollAwareComponent() {
const containerRef = useRef<HTMLDivElement>(null);
const [showScrollToTop, setShowScrollToTop] = useState(false);
useScrollCallback({
element: containerRef.current,
threshold: 100,
onScrollDown: () => setShowScrollToTop(true),
onScrollTop: () => setShowScrollToTop(false),
});
return (
<div ref={containerRef} style={{ overflow: "auto", height: 400 }}>
{/* Scrollable content */}
{showScrollToTop && (
<button onClick={() => scrollToTop()}>Back to Top</button>
)}
</div>
);
}
Options
| Option | Type | Default | Description |
|---|
| element | HTMLElement | null | The scrollable element to monitor |
| threshold | number | 50 | Pixel threshold to trigger onScrollDown |
| onScrollDown | () => void | - | Callback when scrolled past threshold |
| onScrollTop | () => void | - | Callback when scrolled back to top |
Returns
| Property | Type | Description |
|---|
| lastScrollTop | number | Last recorded scroll position |
| hasTriggeredScrollDown | boolean | Whether onScrollDown has been triggered |
Behavior
- One-time Trigger:
onScrollDown only fires once per scroll session (until returning to top)
- Top Detection:
onScrollTop fires when scroll position returns to 0
- Passive Listener: Uses passive event listener for better scroll performance
- Window Fallback: Falls back to window scroll if no element is provided
import { useScrollCallback } from "@servicetitan/ext-atlas";
function ChatWithCollapsibleHeader() {
const contentRef = useRef<HTMLDivElement>(null);
const [headerCollapsed, setHeaderCollapsed] = useState(false);
useScrollCallback({
element: contentRef.current,
threshold: 50,
onScrollDown: () => setHeaderCollapsed(true),
onScrollTop: () => setHeaderCollapsed(false),
});
return (
<div className="chat-container">
<Header
title="Atlas"
className={headerCollapsed ? "collapsed" : ""}
/>
<div ref={contentRef} className="chat-content">
{/* Messages */}
</div>
</div>
);
}
import { useScrollCallback } from "@servicetitan/ext-atlas";
function LazyImageGallery() {
const galleryRef = useRef<HTMLDivElement>(null);
const [imagesLoaded, setImagesLoaded] = useState(false);
useScrollCallback({
element: galleryRef.current,
threshold: 200,
onScrollDown: () => {
if (!imagesLoaded) {
loadHighResImages();
setImagesLoaded(true);
}
},
});
return (
<div ref={galleryRef} className="gallery">
{images.map((img) => (
<img
key={img.id}
src={imagesLoaded ? img.highRes : img.placeholder}
/>
))}
</div>
);
}
Last modified on February 3, 2026