# Scroll Containers

Create scrolling containers using the scroll snap features from Tailwind.

## Scroll Snap

Implements Tailwind's [Scroll Snap Alignment](https://tailwindcss.com/docs/scroll-snap-align) utility classes.

```tsx
export default function Default() {
	return (
		<div className="w-full">
			{/* Scroll Container */}
			<div className="snap-x scroll-px-4 snap-mandatory scroll-smooth flex gap-4 overflow-x-auto px-4 py-10">
				{/* Generate a array of 8 items; loop through each item */}
				{Array.from({ length: 8 }).map((_, i) => (
					// Each scrollable card element
					<div key={i} className="snap-start shrink-0 card preset-filled py-20 w-40 md:w-80 text-center">
						<span>{i + 1}</span>
					</div>
				))}
			</div>
		</div>
	);
}

```

## Carousels

Using Scroll Containers, we can create a fully functional carousel, complete with thumbnail selection.

```tsx
import { useRef } from 'react';
import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react';

export default function Carousel() {
	const generatedArray = Array.from({ length: 6 });
	const elemCarouselRef = useRef<HTMLDivElement>(null);

	function carouselLeft() {
		if (!elemCarouselRef.current) return;
		const elemCarousel = elemCarouselRef.current;
		const x =
			elemCarousel.scrollLeft === 0
				? elemCarousel.clientWidth * elemCarousel.childElementCount // loop
				: elemCarousel.scrollLeft - elemCarousel.clientWidth; // step left
		elemCarousel.scroll(x, 0);
	}

	function carouselRight() {
		if (!elemCarouselRef.current) return;
		const elemCarousel = elemCarouselRef.current;
		const x =
			elemCarousel.scrollLeft === elemCarousel.scrollWidth - elemCarousel.clientWidth
				? 0 // loop
				: elemCarousel.scrollLeft + elemCarousel.clientWidth; // step right
		elemCarousel.scroll(x, 0);
	}

	function carouselThumbnail(index: number) {
		if (elemCarouselRef.current) {
			elemCarouselRef.current.scroll(elemCarouselRef.current.clientWidth * index, 0);
		}
	}

	return (
		<div className="w-full">
			{/* Carousel */}
			<div className="card p-4 grid grid-cols-[auto_1fr_auto] gap-4 items-center">
				{/* Button: Left */}
				<button type="button" className="btn-icon preset-filled" onClick={carouselLeft} title="Previous slide" aria-label="Previous slide">
					<ArrowLeftIcon size={16} />
				</button>
				{/* Full Images */}
				<div ref={elemCarouselRef} className="snap-x snap-mandatory scroll-smooth flex overflow-x-auto">
					{/* Loop X many times. */}
					{generatedArray.map((_, i) => (
						<img
							key={i}
							className="snap-center w-[1024px] rounded-container"
							src={`https://picsum.photos/seed/${i + 1}/1024/768`}
							alt={`full-${i}`}
							loading="lazy"
						/>
					))}
				</div>
				{/* Button: Right */}
				<button type="button" className="btn-icon preset-filled" onClick={carouselRight} title="Next slide" aria-label="Next slide">
					<ArrowRightIcon size={16} />
				</button>
			</div>
			{/* Thumbnails */}
			<div className="card p-4 grid grid-cols-6 gap-4">
				{/* Loop X many times. */}
				{generatedArray.map((_, i) => (
					<button key={i} type="button" onClick={() => carouselThumbnail(i)}>
						<img
							className="rounded-container hover:brightness-125"
							src={`https://picsum.photos/seed/${i + 1}/256`}
							alt={`thumb-${i}`}
							loading="lazy"
						/>
					</button>
				))}
			</div>
		</div>
	);
}

```

## Multi-Column

Using Scroll Containers, we can scroll sets of items.

```tsx
import { useRef } from 'react';
import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react';

interface Movie {
	name: string;
	imageUrl: string;
	url: string;
}

// Data and images via: https://www.themoviedb.org/
const movies: Movie[] = [
	{
		name: 'The Flash',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/rktDFPbfHfUbArZ6OOOKsXcv0Bm.jpg',
		url: 'https://www.themoviedb.org/movie/298618-the-flash',
	},
	{
		name: 'Guardians of the Galaxy Vol. 3',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/r2J02Z2OpNTctfOSN1Ydgii51I3.jpg',
		url: 'https://www.themoviedb.org/movie/447365-guardians-of-the-galaxy-vol-3',
	},
	{
		name: 'Black Panther: Wakanda Forever',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/sv1xJUazXeYqALzczSZ3O6nkH75.jpg',
		url: 'https://www.themoviedb.org/movie/505642-black-panther-wakanda-forever',
	},
	{
		name: 'Avengers: Infinity War',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg',
		url: 'https://www.themoviedb.org/movie/299536-avengers-infinity-war',
	},
	{
		name: 'Spider-Man: No Way Home',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/1g0dhYtq4irTY1GPXvft6k4YLjm.jpg',
		url: 'https://www.themoviedb.org/movie/634649-spider-man-no-way-home',
	},
	{
		name: 'The Batman',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/74xTEgt7R36Fpooo50r9T25onhq.jpg',
		url: 'https://www.themoviedb.org/movie/414906-the-batman',
	},
	{
		name: 'Iron Man',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/78lPtwv72eTNqFW9COBYI0dWDJa.jpg',
		url: 'https://www.themoviedb.org/movie/1726-iron-man',
	},
	{
		name: 'Venom: Let There Be Carnage',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/rjkmN1dniUHVYAtwuV3Tji7FsDO.jpg',
		url: 'https://www.themoviedb.org/movie/580489-venom-let-there-be-carnage',
	},
	{
		name: 'Deadpool',
		imageUrl: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/3E53WEZJqP6aM84D8CckXx4pIHw.jpg',
		url: 'https://www.themoviedb.org/movie/293660-deadpool',
	},
];

export default function MultiColumn() {
	const elemMoviesRef = useRef<HTMLDivElement>(null);

	function multiColumnLeft() {
		if (!elemMoviesRef.current) return;
		const elemMovies = elemMoviesRef.current;
		let x = elemMovies.scrollWidth;
		if (elemMovies.scrollLeft !== 0) {
			x = elemMovies.scrollLeft - elemMovies.clientWidth;
		}
		elemMovies.scroll(x, 0);
	}

	function multiColumnRight() {
		if (!elemMoviesRef.current) return;
		const elemMovies = elemMoviesRef.current;
		let x = 0;
		// -1 is used because different browsers use different methods to round scrollWidth pixels.
		if (elemMovies.scrollLeft < elemMovies.scrollWidth - elemMovies.clientWidth - 1) {
			x = elemMovies.scrollLeft + elemMovies.clientWidth;
		}
		elemMovies.scroll(x, 0);
	}

	return (
		<div className="w-full">
			<div className="grid grid-cols-[auto_1fr_auto] gap-4 items-center">
				{/* Button: Left */}
				<button type="button" className="btn-icon preset-filled" onClick={multiColumnLeft} title="Scroll left" aria-label="Scroll left">
					<ArrowLeftIcon size={16} />
				</button>
				{/* Carousel */}
				<div ref={elemMoviesRef} className="snap-x snap-mandatory scroll-smooth flex gap-2 pb-2 overflow-x-auto">
					{/* Loop through our array of movies. */}
					{movies.map((movie) => (
						<a key={movie.name} href={movie.url} target="_blank" className="shrink-0 w-[28%] snap-start">
							<img
								className="rounded-container-token hover:brightness-125"
								src={movie.imageUrl}
								alt={movie.name}
								title={movie.name}
								loading="lazy"
							/>
						</a>
					))}
				</div>
				{/* Button-Right */}
				<button type="button" className="btn-icon preset-filled" onClick={multiColumnRight} title="Scroll right" aria-label="Scroll right">
					<ArrowRightIcon size={16} />
				</button>
			</div>
		</div>
	);
}

```

> Images courtesy of [The Movie Database](https://www.themoviedb.org/)

## API Reference

Learn more about Tailwind's utility classes for scroll behavior and scroll snap.

\| Feature                                                             | Description                                                         |
\| ------------------------------------------------------------------- | ------------------------------------------------------------------- |
\| [scroll-behavior](https://tailwindcss.com/docs/scroll-behavior)     | Controls the scroll behavior of an element.                         |
\| [scroll-margin](https://tailwindcss.com/docs/scroll-margin)         | Controls the scroll offset around items in a snap container.        |
\| [scroll-padding](https://tailwindcss.com/docs/scroll-padding)       | Controls an element's scroll offset within a snap container.        |
\| [scroll-snap-align](https://tailwindcss.com/docs/scroll-snap-align) | Controls the scroll snap alignment of an element.                   |
\| [scroll-snap-stop](https://tailwindcss.com/docs/scroll-snap-stop)   | Controls whether you can skip past possible snap positions.         |
\| [scroll-snap-type](https://tailwindcss.com/docs/scroll-snap-type)   | Controls how strictly snap points are enforced in a snap container. |
