Skip to main content
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

OptionTypeDefaultDescription
elementHTMLElementnullThe scrollable element to monitor
thresholdnumber50Pixel threshold to trigger onScrollDown
onScrollDown() => void-Callback when scrolled past threshold
onScrollTop() => void-Callback when scrolled back to top

Returns

PropertyTypeDescription
lastScrollTopnumberLast recorded scroll position
hasTriggeredScrollDownbooleanWhether 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

Example: Show/Hide Header on Scroll

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>
  );
}

Example: Lazy Load Images on Scroll

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