Scroll Area
A scroll area provides a scrollable viewport with customizable scrollbars for content that exceeds the container's dimensions.
Features
- Custom scrollbar styling and behavior
- Smooth scrolling with easing functions
- Touch and keyboard navigation support
- Automatic scrollbar hiding when not needed
- Nested scroll area support
- Programmatic scrolling to edges or coordinates
Installation
To use the scroll area machine in your project, run the following command in your command line:
npm install @zag-js/scroll-area @zag-js/react # or yarn add @zag-js/scroll-area @zag-js/react
npm install @zag-js/scroll-area @zag-js/solid # or yarn add @zag-js/scroll-area @zag-js/solid
npm install @zag-js/scroll-area @zag-js/vue # or yarn add @zag-js/scroll-area @zag-js/vue
npm install @zag-js/scroll-area @zag-js/svelte # or yarn add @zag-js/scroll-area @zag-js/svelte
Anatomy
To set up the scroll area correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Required style
It's important to note that the scroll area requires the following styles to be applied to the viewport element to hide the scrollbar:
[data-scope="scroll-area"][data-part="viewport"] { /* hide scrollbar */ scrollbar-width: none; &::-webkit-scrollbar { display: none; } }
Usage
First, import the scroll area package into your project
import * as scrollArea from "@zag-js/scroll-area"
The scroll area package exports two key functions:
machine
— The state machine logic for the scroll area widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the scroll area machine in your project 🔥
import * as scrollArea from "@zag-js/scroll-area" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" function ScrollArea() { const service = useMachine(scrollArea.machine, { id: useId(), }) const api = scrollArea.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <div {...api.getViewportProps()}> <div {...api.getContentProps()}> {/* Your scrollable content here */} </div> </div> <div {...api.getScrollbarProps()}> <div {...api.getThumbProps()} /> </div> </div> ) }
import * as scrollArea from "@zag-js/scroll-area" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" function ScrollArea() { const service = useMachine(scrollArea.machine, { id: createUniqueId(), }) const api = createMemo(() => scrollArea.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <div {...api().getViewportProps()}> <div {...api().getContentProps()}> {/* Your scrollable content here */} </div> </div> <div {...api().getScrollbarProps()}> <div {...api().getThumbProps()} /> </div> </div> ) }
<script setup> import * as scrollArea from "@zag-js/scroll-area" import { useMachine, normalizeProps } from "@zag-js/vue" import { computed, useId } from "vue" const service = useMachine(scrollArea.machine, { id: useId() }) const api = computed(() => scrollArea.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <div v-bind="api.getViewportProps()"> <div v-bind="api.getContentProps()"> <!-- Your scrollable content here --> </div> </div> <div v-bind="api.getScrollbarProps()"> <div v-bind="api.getThumbProps()" /> </div> </div> </template>
<script lang="ts"> import * as scrollArea from "@zag-js/scroll-area" import { useMachine, normalizeProps } from "@zag-js/svelte" const id = $props.id() const service = useMachine(scrollArea.machine, { id }) const api = $derived(scrollArea.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <div {...api.getViewportProps()}> <div {...api.getContentProps()}> <!-- Your scrollable content here --> </div> </div> <div {...api.getScrollbarProps()}> <div {...api.getThumbProps()} /> </div> </div>
Scrolling to edges
You can programmatically scroll to any edge of the scroll area using the
scrollToEdge
method:
// Scroll to bottom api.scrollToEdge({ edge: "bottom" }) // Scroll to top with custom easing api.scrollToEdge({ edge: "top", duration: 500, easing: (t) => t * t, })
Scrolling to coordinates
Use the scrollTo
method to scroll to specific coordinates:
// Scroll to specific position api.scrollTo({ top: 100, left: 50, }) // Scroll with smooth behavior api.scrollTo({ top: 200, behavior: "smooth", duration: 300, })
Checking scroll position
The API provides several properties to check the current scroll state:
// Check if at edges console.log(api.isAtTop) // boolean console.log(api.isAtBottom) // boolean console.log(api.isAtLeft) // boolean console.log(api.isAtRight) // boolean // Check for overflow console.log(api.hasOverflowX) // boolean console.log(api.hasOverflowY) // boolean // Get scroll progress (0-1) const progress = api.getScrollProgress() // { x: number, y: number }
Conditional scrollbar rendering
Only render scrollbars when there's overflow content:
{ api.hasOverflowY && ( <div {...api.getScrollbarProps({ orientation: "vertical" })}> <div {...api.getThumbProps({ orientation: "vertical" })} /> </div> ) } { api.hasOverflowX && ( <div {...api.getScrollbarProps({ orientation: "horizontal" })}> <div {...api.getThumbProps({ orientation: "horizontal" })} /> </div> ) }
Scrollbar state
Get the current state of a scrollbar for styling purposes:
const verticalState = api.getScrollbarState({ orientation: "vertical" }) // Returns: { hovering: boolean, scrolling: boolean, hidden: boolean }
Nested scroll areas
Scroll areas can be nested within each other for complex layouts:
<div {...api.getRootProps()}> <div {...api.getViewportProps()}> <div {...api.getContentProps()}> {/* Nested scroll area */} <ScrollAreaComponent style={{ maxHeight: 200 }}> {/* Inner content */} </ScrollAreaComponent> </div> </div> </div>
Styling guide
Earlier, we mentioned that each scroll area part has a data-part
attribute
added to them to select and style them in the DOM.
Scrolling state
When the user is actively scrolling, a data-scrolling
attribute is set on the
scrollbar elements:
[data-part="scrollbar"][data-scrolling] { /* styles when actively scrolling */ } [data-part="thumb"][data-scrolling] { /* styles for thumb when scrolling */ }
Hover state
When hovering over the scroll area or scrollbar, a data-hover
attribute is
applied:
[data-part="root"][data-hover] { /* styles when hovering over scroll area */ } [data-part="scrollbar"][data-hover] { /* styles when hovering over scrollbar */ }
Hidden state
Scrollbars can be automatically hidden when not needed:
[data-part="scrollbar"][data-hidden] { /* styles for hidden scrollbar */ opacity: 0; pointer-events: none; }
Orientation
Different styles can be applied based on scrollbar orientation:
[data-part="scrollbar"][data-orientation="vertical"] { /* vertical scrollbar styles */ } [data-part="scrollbar"][data-orientation="horizontal"] { /* horizontal scrollbar styles */ }
Methods and Properties
The scroll area's api
exposes the following methods and properties:
Machine Context
The scroll area machine exposes the following context properties:
ids
Partial<{ root: string; viewport: string; content: string; scrollbar: string; thumb: string; }>
The ids of the scroll area elementsdir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The scroll area api
exposes the following methods:
isAtTop
boolean
Whether the scroll area is at the topisAtBottom
boolean
Whether the scroll area is at the bottomisAtLeft
boolean
Whether the scroll area is at the leftisAtRight
boolean
Whether the scroll area is at the righthasOverflowX
boolean
Whether the scroll area has horizontal overflowhasOverflowY
boolean
Whether the scroll area has vertical overflowgetScrollProgress
() => Point
Get the scroll progress as values between 0 and 1scrollToEdge
(details: ScrollToEdgeDetails) => void
Scroll to the edge of the scroll areascrollTo
(details: ScrollToDetails) => void
Scroll to specific coordinatesgetScrollbarState
(props: ScrollbarProps) => ScrollbarState
Returns the state of the scrollbar
Data Attributes
CSS Variables
Edit this page on GitHub