import { ScrollArea } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
export const ScrollAreaBasic = () => (
<ScrollArea.Root height="8.5rem" maxW="lg">
<ScrollArea.Viewport>
<ScrollArea.Content spaceY="4" textStyle="sm">
<LoremIpsum p={3} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>
)
Usage
import { ScrollArea } from "@chakra-ui/react"
<ScrollArea.Root>
<ScrollArea.Viewport>
<ScrollArea.Content />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>
Examples
Variants
Use the variant
prop to change the scrollbar visibility behavior. Values can
be either hover
(default) or always
.
variant="hover"
variant="always"
import { For, ScrollArea, Stack, Text } from "@chakra-ui/react"
import Lorem from "react-lorem-ipsum"
export const ScrollAreaWithVariants = () => (
<Stack gap="8" maxW="lg">
<For each={["hover", "always"]}>
{(variant) => (
<Stack gap="2" key={variant}>
<Text fontWeight="medium">variant="{variant}"</Text>
<ScrollArea.Root height="8rem" variant={variant}>
<ScrollArea.Viewport>
<ScrollArea.Content paddingEnd="3" textStyle="sm">
<Lorem p={4} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
</ScrollArea.Root>
</Stack>
)}
</For>
</Stack>
)
Sizes
Use the size
prop to change the size of the scroll area. This affects the
scrollbar thickness and content padding.
size="xs"
size="sm"
size="md"
size="lg"
import { For, ScrollArea, Stack, Text } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
export const ScrollAreaWithSizes = () => (
<Stack gap="8" maxW="lg">
<For each={["xs", "sm", "md", "lg"]}>
{(size) => (
<Stack gap="2" key={size}>
<Text fontWeight="medium">size="{size}"</Text>
<ScrollArea.Root size={size} height="8rem" variant="always">
<ScrollArea.Viewport>
<ScrollArea.Content paddingEnd="5" textStyle="sm">
<LoremIpsum p={2} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
</ScrollArea.Root>
</Stack>
)}
</For>
</Stack>
)
Horizontal Scrolling
The scroll area automatically supports horizontal scrolling when content overflows horizontally.
import { Flex, ScrollArea } from "@chakra-ui/react"
import { DecorativeBox } from "compositions/lib/decorative-box"
export const ScrollAreaHorizontal = () => (
<ScrollArea.Root width="24rem" size="xs">
<ScrollArea.Viewport>
<ScrollArea.Content py="4">
<Flex gap="4" flexWrap="nowrap">
{Array.from({ length: 12 }, (_, i) => (
<DecorativeBox rounded="sm" key={i} h="20" w="40" flexShrink="0">
Item {i + 1}
</DecorativeBox>
))}
</Flex>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="horizontal" />
<ScrollArea.Corner />
</ScrollArea.Root>
)
Both Directions
When content overflows in both directions, both scrollbars will appear.
import { ScrollArea } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
export const ScrollAreaBothDirections = () => (
<ScrollArea.Root height="12rem" width="lg" size="xs" p="2">
<ScrollArea.Viewport>
<ScrollArea.Content spaceY="4" w="40rem" textStyle="sm">
<LoremIpsum p={3} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="horizontal" />
<ScrollArea.Scrollbar orientation="vertical" />
<ScrollArea.Corner bg="bg" />
</ScrollArea.Root>
)
ScrollArea.Corner
component to show a corner indicator to
fill the intersection of the two scrollbars for a seamless, styled appearance.Scroll Shadow
Add visual feedback when content is scrollable by implementing scroll shadows
that appear at the edges using mask-image
.
import { ScrollArea } from "@chakra-ui/react"
import { DecorativeBox } from "compositions/lib/decorative-box"
const Demo = () => {
return (
<ScrollArea.Root height="20rem" maxW="lg">
<ScrollArea.Viewport
css={{
"--scroll-shadow-size": "4rem",
maskImage:
"linear-gradient(#000,#000,transparent 0,#000 var(--scroll-shadow-size),#000 calc(100% - var(--scroll-shadow-size)),transparent)",
"&[data-at-top]": {
maskImage:
"linear-gradient(180deg,#000 calc(100% - var(--scroll-shadow-size)),transparent)",
},
"&[data-at-bottom]": {
maskImage:
"linear-gradient(0deg,#000 calc(100% - var(--scroll-shadow-size)),transparent)",
},
}}
>
<ScrollArea.Content spaceY="4">
{Array.from({ length: 10 }, (_, i) => (
<DecorativeBox key={i} h="20">
Item {i + 1}
</DecorativeBox>
))}
</ScrollArea.Content>
</ScrollArea.Viewport>
</ScrollArea.Root>
)
}
Thumb Styling
Customize the appearance of the scrollbar thumb with different styles and colors.
import { ScrollArea } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
export const ScrollAreaWithThumbStyling = () => (
<ScrollArea.Root height="8rem" maxW="2xl" variant="always">
<ScrollArea.Viewport>
<ScrollArea.Content spaceY="4" pe="2">
<LoremIpsum p={2} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar bg="red.subtle">
<ScrollArea.Thumb bg="red.solid" />
</ScrollArea.Scrollbar>
</ScrollArea.Root>
)
Stick to Bottom
Implement chat-like behavior where new content automatically scrolls to the bottom, but allows manual scrolling.
This example uses use-stick-to-bottom
to scroll pinning.
"use client"
import {
Box,
Button,
ButtonGroup,
IconButton,
ScrollArea,
VStack,
} from "@chakra-ui/react"
import { DecorativeBox } from "compositions/lib/decorative-box"
import { useState } from "react"
import { LuArrowDown } from "react-icons/lu"
import { useStickToBottom } from "use-stick-to-bottom"
const Demo = () => {
const sticky = useStickToBottom()
const [messages, setMessages] = useState<string[]>([
"Message 1 - 10:00:00",
"Message 2 - 10:00:01",
"Message 3 - 10:00:02",
"Message 4 - 10:00:03",
"Message 5 - 10:00:04",
"Message 6 - 10:00:05",
"Message 7 - 10:00:06",
"Message 8 - 10:00:07",
"Message 9 - 10:00:08",
"Message 10 - 10:00:09",
])
const addMessage = () => {
const newMessage = `Message ${messages.length + 1} - ${new Date().toLocaleTimeString()}`
setMessages((prev) => [...prev, newMessage])
}
const addMultipleMessages = () => {
const newMessages = Array.from(
{ length: 5 },
(_, i) =>
`Batch message ${messages.length + i + 1} - ${new Date().toLocaleTimeString()}`,
)
setMessages((prev) => [...prev, ...newMessages])
}
const removeMessage = () => {
setMessages((prev) => prev.slice(0, -1))
}
return (
<VStack gap="4" align="stretch" width="20rem">
<ButtonGroup gap="2" size="sm" variant="outline">
<Button onClick={addMessage}>Add Message</Button>
<Button onClick={addMultipleMessages}>Add 5 Messages</Button>
<Button onClick={removeMessage}>Remove Message</Button>
</ButtonGroup>
<ScrollArea.Root
maxHeight="20rem"
width="full"
borderWidth="1px"
rounded="l2"
size="xs"
>
<ScrollArea.Viewport ref={sticky.scrollRef}>
<ScrollArea.Content ref={sticky.contentRef}>
<VStack gap="2" p="3" align="stretch">
{messages.map((message, index) => (
<DecorativeBox key={index} h="12">
{message}
</DecorativeBox>
))}
</VStack>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
{!sticky.isAtBottom && (
<Box position="absolute" bottom="4" right="4" zIndex="10">
<IconButton
size="sm"
onClick={() => {
sticky.scrollToBottom()
}}
colorScheme="blue"
variant="solid"
>
<LuArrowDown />
</IconButton>
</Box>
)}
</ScrollArea.Root>
</VStack>
)
}
Virtualization
Handle large datasets efficiently by rendering only visible items using
@tanstack/react-virtual
.
"use client"
import { ScrollArea } from "@chakra-ui/react"
import { type VirtualItem, useVirtualizer } from "@tanstack/react-virtual"
import { DecorativeBox } from "compositions/lib/decorative-box"
import React, { useCallback, useMemo, useRef } from "react"
const Demo = () => {
const scrollRef = useRef<HTMLDivElement>(null)
const items = useMemo(
() =>
Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `Item ${i + 1}`,
})),
[],
)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 80,
overscan: 5,
})
const contentProps = useMemo(
(): React.ComponentProps<"div"> => ({
style: {
height: `${virtualizer.getTotalSize()}px`,
width: "full",
position: "relative",
},
}),
[virtualizer],
)
const getItemProps = useCallback(
(item: VirtualItem): React.ComponentProps<"div"> => ({
style: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
paddingBottom: 4,
height: `${item.size}px`,
transform: `translateY(${item.start}px)`,
},
}),
[],
)
return (
<ScrollArea.Root height="20rem" maxWidth="xl">
<ScrollArea.Viewport ref={scrollRef}>
<ScrollArea.Content {...contentProps}>
{virtualizer.getVirtualItems().map((virtualItem) => {
const item = items[virtualItem.index]
return (
<div key={virtualItem.key} {...getItemProps(virtualItem)}>
<DecorativeBox w="full">{item.name}</DecorativeBox>
</div>
)
})}
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar bg="transparent" />
</ScrollArea.Root>
)
}
Store
Use the scroll area with external state management and programmatic control.
"use client"
import { ScrollArea, useScrollArea } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
const Demo = () => {
const scrollArea = useScrollArea()
return (
<ScrollArea.RootProvider value={scrollArea} height="8.5rem">
<ScrollArea.Viewport>
<ScrollArea.Content spaceY="4">
<LoremIpsum p={3} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
</ScrollArea.RootProvider>
)
}
Scroll to Side
Programmatically navigate through content by scrolling to different sides and directions.
"use client"
import {
Button,
ButtonGroup,
ScrollArea,
Stack,
useScrollArea,
} from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
const Demo = () => {
const scrollArea = useScrollArea()
return (
<Stack gap="8" align="flex-start" maxW="xl">
<ButtonGroup variant="outline" justify="center" size="sm">
<Button
onClick={() =>
scrollArea.scrollToEdge({ edge: "bottom", behavior: "smooth" })
}
>
Scroll to bottom
</Button>
<Button
onClick={() =>
scrollArea.scrollToEdge({ edge: "top", behavior: "smooth" })
}
>
Scroll to top
</Button>
</ButtonGroup>
<ScrollArea.RootProvider value={scrollArea} height="8rem" width="24rem">
<ScrollArea.Viewport>
<ScrollArea.Content>
<LoremIpsum p={3} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
</ScrollArea.RootProvider>
</Stack>
)
}
Scroll to Position
Jump to specific positions or items within the scrollable area with smooth animations.
"use client"
import { Button, ScrollArea, Stack, useScrollArea } from "@chakra-ui/react"
import LoremIpsum from "react-lorem-ipsum"
const Demo = () => {
const scrollArea = useScrollArea()
return (
<Stack gap="8" align="flex-start" maxW="xl">
<Button
variant="outline"
size="sm"
onClick={() => scrollArea.scrollTo({ top: 200, behavior: "smooth" })}
>
Scroll to 100px
</Button>
<ScrollArea.RootProvider value={scrollArea} height="8rem" width="24rem">
<ScrollArea.Viewport>
<ScrollArea.Content>
<LoremIpsum p={3} />
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
</ScrollArea.RootProvider>
</Stack>
)
}
RTL Support
The scroll area component fully supports Right-to-Left (RTL) languages like Arabic and Hebrew.
import { LocaleProvider, ScrollArea } from "@chakra-ui/react"
const arabicText = [
"مرحباً بكم في نظام التمرير المخصص",
"هذا مثال على النص العربي في منطقة التمرير",
"يدعم النظام اللغات التي تُكتب من اليمين إلى اليسار",
"التمرير الأفقي يعمل بشكل صحيح مع النصوص العربية",
"يمكنك رؤية كيف تتكيف أشرطة التمرير مع اتجاه النص",
"النظام يدعم التمرير العمودي والأفقي في نفس الوقت",
"يمكن تخصيص مظهر أشرطة التمرير حسب التصميم المطلوب",
"التفاعل مع أشرطة التمرير سهل ومريح للمستخدم",
"يعمل النظام بسلاسة على جميع المتصفحات الحديثة",
"يمكن دمج هذا المكون مع مكونات أخرى بسهولة",
"الأداء محسّن للتعامل مع كميات كبيرة من المحتوى",
]
const Demo = () => {
return (
<LocaleProvider locale="ar-AE">
<ScrollArea.Root height="8rem" width="24rem" size="sm">
<ScrollArea.Viewport>
<ScrollArea.Content p="2">
{arabicText.map((text, i) => (
<span key={i}>{text}</span>
))}
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar />
<ScrollArea.Corner />
</ScrollArea.Root>
</LocaleProvider>
)
}
Props
Root
Prop | Default | Type |
---|---|---|
colorPalette | 'gray' | 'gray' | 'red' | 'orange' | 'yellow' | 'green' | 'teal' | 'blue' | 'cyan' | 'purple' | 'pink' The color palette of the component |
variant | 'hover' | 'hover' | 'always' The variant of the component |
size | 'md' | 'xs' | 'sm' | 'md' | 'lg' The size of the component |
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
unstyled | boolean Whether to remove the component's style. | |
ids | Partial<{ root: string; viewport: string; content: string; scrollbar: string; thumb: string }> The ids of the scroll area elements |
Viewport
Prop | Default | Type |
---|---|---|
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Content
Prop | Default | Type |
---|---|---|
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Scrollbar
Prop | Default | Type |
---|---|---|
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
orientation | Orientation |
Thumb
Prop | Default | Type |
---|---|---|
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Corner
Prop | Default | Type |
---|---|---|
as | React.ElementType The underlying element to render. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |