BetaPublic beta — Read the release notes
Documentation

Scroll Area

Basic scroll area example demonstrating core behavior.

Package
@solidiom/scroll-area
Version
0.0.1-next.0
Status
stable

Examples

import * as ScrollArea from "@solidiom/scroll-area"

;<ScrollArea.Root type="hover">
  <ScrollArea.Viewport style={{ height: 300 }}>
    <div style={{ padding: 16 }}>
      {Array.from({ length: 50 }, (_, i) => (
        <p key={i}>Line {i + 1} of scrollable content.</p>
      ))}
    </div>
  </ScrollArea.Viewport>

  <ScrollArea.Scrollbar orientation="vertical">
    <ScrollArea.Thumb />
  </ScrollArea.Scrollbar>

  <ScrollArea.Scrollbar orientation="horizontal">
    <ScrollArea.Thumb />
  </ScrollArea.Scrollbar>
</ScrollArea.Root>

The scrollbar visibility is controlled by the type prop: “auto” (overflow only), “always”, “hover” (default), or “scroll” (visible during scroll then hides after scrollHideDelay ms).

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
View source
        export function Root(props: ScrollAreaRootProps) {
  const type = props.type ?? "hover"
  const scrollHideDelay = props.scrollHideDelay ?? 600

  const [viewportRef, setViewportRef] = createSignal<HTMLDivElement | undefined>(undefined)
  const [scrollHeight, setScrollHeight] = createSignal(0)
  const [scrollWidth, setScrollWidth] = createSignal(0)
  const [viewportHeight, setViewportHeight] = createSignal(0)
  const [viewportWidth, setViewportWidth] = createSignal(0)
  const [scrollTop, setScrollTop] = createSignal(0)
  const [scrollLeft, setScrollLeft] = createSignal(0)
  const [isHovered, setIsHovered] = createSignal(false)
  const [isScrolling, setIsScrolling] = createSignal(false)

  let scrollTimer: ReturnType<typeof setTimeout> | undefined

  const updateMeasurements = () => {
    const el = viewportRef()
    if (!el) return
    setScrollHeight(el.scrollHeight)
    setScrollWidth(el.scrollWidth)
    setViewportHeight(el.clientHeight)
    setViewportWidth(el.clientWidth)
  }

  const updateScrollPosition = () => {
    const el = viewportRef()
    if (!el) return
    setScrollTop(el.scrollTop)
    setScrollLeft(el.scrollLeft)
  }

  const notifyScrollStart = () => {
    setIsScrolling(true)
    if (scrollTimer !== undefined) clearTimeout(scrollTimer)
    scrollTimer = setTimeout(() => {
      setIsScrolling(false)
    }, scrollHideDelay)
  }

  return (
    <ScrollAreaContext
      value={{
        viewportRef,
        setViewportRef,
        scrollHeight,
        scrollWidth,
        viewportHeight,
        viewportWidth,
        scrollTop,
        scrollLeft,
        type,
        isHovered,
        isScrolling,
        scrollHideDelay,
        updateMeasurements,
        updateScrollPosition,
        notifyScrollStart,
      }}
    >
      <div
        class={props.class}
        style={{
          position: "relative",
          overflow: "hidden",
          ...(typeof props.style === "object" ? props.style : {}),
        }}
        onPointerEnter={() => setIsHovered(true)}
        onPointerLeave={() => setIsHovered(false)}
        {...applySemanticAttrs({
          scope: "scroll-area",
          part: "root",
        })}
      >
        {props.children}
      </div>
    </ScrollAreaContext>
  )
}