A cross-browser solution for styled scroll areas: it relies on native scrolling for accessibility and performance while exposing parts (viewport, scrollbar, thumb, corner) for styling.
@radix-ui/react-scroll-area
lucide-react1import * as React from 'react'
2
3import { ScrollArea } from '@/components/ui/scroll-area'
4import { Separator } from '@/components/ui/separator'
5
6const techItems = {
7 'Programming Languages': [
8 'JavaScript',
9 'Python',
10 'TypeScript',
11 'Go',
12 'Rust',
13 ],
14 'Frontend Frameworks': ['React', 'Next.js', 'Vue.js', 'Svelte', 'Angular'],
15 'Backend Frameworks': [
16 'Express.js',
17 'Django',
18 'FastAPI',
19 'NestJS',
20 'Spring Boot',
21 ],
22 Databases: ['PostgreSQL', 'MongoDB', 'MySQL'],
23 'DevOps & Tools': [
24 'Docker',
25 'Kubernetes',
26 'GitHub Actions',
27 'AWS',
28 'Vercel',
29 'DigitalOcean',
30 ],
31}
32
33export function TechStackScrollDemo() {
34 return (
35 <ScrollArea className="border-border h-[280px] w-60 rounded-lg border">
36 <div className="p-4">
37 <h3 className="mb-4 text-lg font-semibold text-black">
38 Technology Stack
39 </h3>
40 {Object.entries(techItems).map(([category, items]) => (
41 <React.Fragment key={category}>
42 <div className="border-border sticky top-0 z-10 border-b bg-gray-200 p-2">
43 <h4 className="text-primary text-sm font-medium">
44 {category}
45 </h4>
46 </div>
47
48 {items.map((item, index) => (
49 <React.Fragment key={item}>
50 <div className="text-gray py-2 text-sm">
51 {item}
52 </div>
53 {index < items.length - 1 && <Separator />}
54 </React.Fragment>
55 ))}
56 </React.Fragment>
57 ))}
58 </div>
59 </ScrollArea>
60 )
61}
62Install the following dependencies:
Alice
Hey, did you see the new release?
Olivia
Not yet! Was it v1.3.0?
Aarav
Almost, v1.2.0-beta.50, but it has some cool fixes.
Emma
Awesome, I need to check the changelog.
dollie
Hey, did you see the new release?
Sophia
Awesome, I need to check the changelog.
Alice
Hey, did you see the new release?
Olivia
Not yet! Was it v1.3.0?
Aarav
Almost, v1.2.0-beta.50, but it has some cool fixes.
Emma
Awesome, I need to check the changelog.
1import * as React from 'react'
2import Image from 'next/image'
3
4import { Avatar, AvatarFallback } from '@/components/ui/avatar'
5import { ScrollArea } from '@/components/ui/scroll-area'
6
7const messages = [
8 {
9 user: 'Alice',
10 text: 'Hey, did you see the new release?',
11 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F2244af71ad0c25f2cb0a8efa167491fb.png&w=280&q=85',
12 },
13 {
14 user: 'Olivia',
15 text: 'Not yet! Was it v1.3.0?',
16 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F06d29f74c2f85239efe3f9ade1b96da7.png&w=280&q=85',
17 },
18 {
19 user: 'Aarav',
20 text: 'Almost, v1.2.0-beta.50, but it has some cool fixes.',
21 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F119d9abaee7a1e987571f0fe776bd1a5.png&w=280&q=85',
22 },
23 {
24 user: 'Emma',
25 text: 'Awesome, I need to check the changelog.',
26 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F7d24c27f40be0d43618f6d49e26a3288.png&w=280&q=85',
27 },
28 {
29 user: 'dollie',
30 text: 'Hey, did you see the new release?',
31 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2Fbb3d2a58ea153b635a4951d82affb4db.png&w=280&q=85',
32 },
33 {
34 user: 'Sophia',
35 text: 'Awesome, I need to check the changelog.',
36 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F644dfc35027924a6e5dfbcad653be697.png&w=280&q=85',
37 },
38 {
39 user: 'Alice',
40 text: 'Hey, did you see the new release?',
41 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F2244af71ad0c25f2cb0a8efa167491fb.png&w=280&q=85',
42 },
43 {
44 user: 'Olivia',
45 text: 'Not yet! Was it v1.3.0?',
46 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F06d29f74c2f85239efe3f9ade1b96da7.png&w=280&q=85',
47 },
48 {
49 user: 'Aarav',
50 text: 'Almost, v1.2.0-beta.50, but it has some cool fixes.',
51 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F119d9abaee7a1e987571f0fe776bd1a5.png&w=280&q=85',
52 },
53 {
54 user: 'Emma',
55 text: 'Awesome, I need to check the changelog.',
56 image: 'https://tabler.io/_next/image?url=%2Favatars%2Fdefault%2F7d24c27f40be0d43618f6d49e26a3288.png&w=280&q=85',
57 },
58]
59
60export function ChatScrollDefault() {
61 return (
62 <ScrollArea className="border-border h-80 w-64 rounded-xl border bg-gray-50 sm:w-80">
63 <div className="space-y-4 p-4">
64 {messages.map((message, index) => (
65 <div key={index} className="flex items-start space-x-3">
66 <Avatar size={'lg'}>
67 <Image
68 src={message.image}
69 alt="Avatar"
70 width={24}
71 height={24}
72 className="h-full w-full"
73 />
74 <AvatarFallback className="text-primary bg-gray-200 text-xs"></AvatarFallback>
75 </Avatar>
76 <div>
77 <p className="text-primary text-sm font-semibold">
78 {message.user}
79 </p>
80 <p className="text-xs">{message.text}</p>
81 </div>
82 </div>
83 ))}
84 </div>
85 </ScrollArea>
86 )
87}
881import * as React from 'react'
2import Image from 'next/image'
3
4import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
5
6export interface Artwork {
7 artist: string
8 src: string
9}
10export const works: Artwork[] = [
11 {
12 artist: 'Jenna',
13 src: 'https://images.unsplash.com/photo-1710609942195-b9dab8f48fc6?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&fm=jpg&q=60&w=3000',
14 },
15 {
16 artist: 'Pawel Czerwiński',
17 src: 'https://images.unsplash.com/photo-1492112007959-c35ae067c37b?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=684',
18 },
19 {
20 artist: 'Lara',
21 src: 'https://images.unsplash.com/photo-1494376877685-d3d2559d4f82?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&fm=jpg&q=60&w=3000',
22 },
23 {
24 artist: 'Joao Santos',
25 src: 'https://images.unsplash.com/photo-1538998073820-4dfa76300194?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=687',
26 },
27 {
28 artist: 'Ricardo Angel',
29 src: 'https://images.unsplash.com/photo-1503332132010-d1b77a049ddd?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDB8fGRhcmt8ZW58MHx8MHx8fDA%3D&auto=format&fit=crop&q=60&w=500',
30 },
31]
32
33export function ScrollAreaHorizontalDemo() {
34 return (
35 <ScrollArea className="border-border w-64 rounded-lg border whitespace-nowrap sm:w-96">
36 <div className="flex w-max space-x-4 p-4">
37 {works.map((artwork) => (
38 <figure key={artwork.artist} className="shrink-0">
39 <div className="aspect-[3/4] h-72 overflow-hidden rounded-lg">
40 <Image
41 src={artwork.src}
42 alt={`Artwork by ${artwork.artist}`}
43 className="h-full w-full object-cover transition-transform duration-300"
44 width={216}
45 height={288}
46 priority
47 />
48 </div>
49 <figcaption className="text-muted-foreground pt-2 text-xs">
50 Art by{' '}
51 <span className="text-foreground font-semibold">
52 {artwork.artist}
53 </span>
54 </figcaption>
55 </figure>
56 ))}
57 </div>
58 <ScrollBar orientation="horizontal" />
59 </ScrollArea>
60 )
61}
62Contains all the parts of a scroll area.
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | false |
| scrollHideDelay | number | 600 |
The vertical scrollbar. Add a second Scrollbar with an orientation prop to enable horizontal scrolling.
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | false |
| orientation | vertical, horizontal | vertical |
| Data attribute | Values |
|---|---|
| [data-state] | visible, hidden |
| [data-orientation] | vertical, horizontal |