Scroll Snap
Implements Tailwind’s Scroll Snap Aligment utility classes.
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 <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 -->    {      Array.from({ length: 8 }).map((_, 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>      ))    }  </div></div>Carousels
Using Scroll Containers, we can create a fully functional carousel, complete with thumbnail selection.
---import { ArrowLeft, ArrowRight } from 'lucide-react';
const generatedArray = Array.from({ length: 6 });---
<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" data-carousel-left>      <ArrowLeft size={16} />    </button>    <!-- Full Images -->    <div data-carousel class="snap-x snap-mandatory scroll-smooth flex overflow-x-auto">      <!-- Loop X many times. -->      {        generatedArray.map((_, i: number) => (          <img            class="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" class="btn-icon preset-filled" data-carousel-right>      <ArrowRight size={16} />    </button>  </div>  <!-- Thumbnails -->  <div class="card p-4 grid grid-cols-6 gap-4">    <!-- Loop X many times. -->    {      generatedArray.map((_, i: number) => (        <button type="button" data-thumbnail>          <img            class="rounded-container hover:brightness-125"            src={`https://picsum.photos/seed/${i + 1}/256`}            alt={`thumb-${i}`}            loading="lazy"          />        </button>      ))    }  </div></div>
<script>  // Query Element References  let elemCarousel: HTMLDivElement | null = document.querySelector('[data-carousel]');  let elemCarouselLeft: HTMLButtonElement | null = document.querySelector('[data-carousel-left]');  let elemCarouselRight: HTMLButtonElement | null = document.querySelector('[data-carousel-right]');  let elemThumbnails: NodeListOf<HTMLElement> = document.querySelectorAll('[data-thumbnail]');
  // Set Left/Right arrow click handlers  elemCarouselLeft?.addEventListener('click', () => carouselLeft());  elemCarouselRight?.addEventListener('click', () => carouselRight());
  // Set thumbnail click handler  if (elemThumbnails.length > 0) {    elemThumbnails.forEach((elemButton: HTMLElement, index: number) => {      elemButton?.addEventListener('click', () => carouselThumbnail(index));    });  }
  /** On navigation left, scroll the container */  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);  }
  /** On navigation right, scroll the container */  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);  }
  /** On thumbnail click, scroll large image into view */  function carouselThumbnail(index: number) {    if (elemCarousel) elemCarousel.scroll(elemCarousel.clientWidth * index, 0);  }</script>Multi-Column
Using Scroll Containers, we can scroll sets of items.
---import { ArrowLeft, ArrowRight } from 'lucide-react';
interface Movie {  name: string;  imageUrl: string;  url: string;}
// Data and images via: https://www.themoviedb.org/export 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'  }];---
<div class="w-ful">  <div class="grid grid-cols-[auto_1fr_auto] gap-4 items-center">    <!-- Button: Left -->    <button type="button" class="btn-icon preset-filled" data-multi-column-left>      <ArrowLeft size={16} />    </button>    <!-- Carousel -->    <div data-multi-column class="snap-x snap-mandatory scroll-smooth flex gap-2 pb-2 overflow-x-auto">      <!-- Loop through our array of movies. -->      {        movies.map((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>        ))      }    </div>    <!-- Button-Right -->    <button type="button" class="btn-icon preset-filled" data-multi-column-right>      <ArrowRight size={16} />    </button>  </div></div>
<script>  // Query Element References  let elemMovies: HTMLDivElement | null = document.querySelector('[data-multi-column]')!;  let elemBtnLeft: HTMLButtonElement | null = document.querySelector('[data-multi-column-left]');  let elemBtnRight: HTMLButtonElement | null = document.querySelector('[data-multi-column-right]');
  // Add Button click handlers  elemBtnLeft?.addEventListener('click', () => multiColumnLeft());  elemBtnRight?.addEventListener('click', () => multiColumnRight());
  /** Handles the left scroll event. */  function multiColumnLeft() {    if (!elemMovies) return;    let x = elemMovies.scrollWidth;    if (elemMovies.scrollLeft !== 0) x = elemMovies.scrollLeft - elemMovies.clientWidth;    elemMovies.scroll(x, 0);  }
  /** Handles the right scroll event. */  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>Images courtesy of The Movie Database
API Reference
Learn more about Tailwind’s utility classes for scroll behavior and scroll snap.
| Feature | Description | 
|---|---|
| scroll-behavior | Controls the scroll behavior of an element. | 
| scroll-margin | Controls the scroll offset around items in a snap container. | 
| scroll-padding | Controls an element’s scroll offset within a snap container. | 
| scroll-snap-align | Controls the scroll snap alignment of an element. | 
| scroll-snap-stop | Controls whether you can skip past possible snap positions. | 
| scroll-snap-type | Controls how strictly snap points are enforced in a snap container. |