# 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.

```svelte
<div class="w-full">
	<!-- Scroll Container -->
	<div class="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 -->
		{#each Array.from({ length: 8 }) as _, i}
			<!-- Each scrollable card element -->
			<div class="snap-start shrink-0 card preset-filled py-20 w-40 md:w-80 text-center">
				<span>{i + 1}</span>
			</div>
		{/each}
	</div>
</div>

```

## Carousels

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

```svelte
<script lang="ts">
	import { ArrowLeftIcon, ArrowRightIcon } from '@lucide/svelte';

	const generatedArray = Array.from({ length: 6 });

	let elemCarousel: HTMLDivElement;

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

	function carouselRight() {
		if (!elemCarousel) return;
		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 (elemCarousel) {
			elemCarousel.scroll(elemCarousel.clientWidth * index, 0);
		}
	}
</script>

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

```

## Multi-Column

Using Scroll Containers, we can scroll sets of items.

```svelte
<script lang="ts">
	import { ArrowLeftIcon, ArrowRightIcon } from '@lucide/svelte';

	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',
		},
	];

	let elemMovies: HTMLDivElement;

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

	function multiColumnRight() {
		if (!elemMovies) return;
		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);
	}
</script>

<div class="w-full">
	<div class="grid grid-cols-[auto_1fr_auto] gap-4 items-center">
		<!-- Button: Left -->
		<button type="button" class="btn-icon preset-filled" onclick={multiColumnLeft} title="Scroll left" aria-label="Scroll left">
			<ArrowLeftIcon size={16} />
		</button>
		<!-- Carousel -->
		<div bind:this={elemMovies} class="snap-x snap-mandatory scroll-smooth flex gap-2 pb-2 overflow-x-auto">
			<!-- Loop through our array of movies. -->
			{#each movies as movie}
				<a href={movie.url} target="_blank" class="shrink-0 w-[28%] snap-start">
					<img
						class="rounded-container-token hover:brightness-125"
						src={movie.imageUrl}
						alt={movie.name}
						title={movie.name}
						loading="lazy"
					/>
				</a>
			{/each}
		</div>
		<!-- Button-Right -->
		<button type="button" class="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. |
