Pure TailwindMIT licensed

Tailwind Vertical Image Carousel with Y-Axis Scroll Snap

A carousel that scrolls up and down instead of left to right — sidebar widgets, story feeds, creative portfolios. Tailwind ships snap-y as readily as snap-x, so the only genuinely vertical concern is giving the scroller a height and stopping its scroll from chaining to the page.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tcar-12 w-full min-h-svh grid place-items-center bg-tcar12-bg font-tcar12-sans px-5 py-10" aria-label="Vertical image carousel">
  <ul tabindex="0" role="group" aria-label="Gallery, scroll up and down" class="tcar12-scroller list-none w-[min(340px,100%)] h-[min(560px,80svh)] overflow-y-auto m-0 p-0 rounded-[20px] snap-y snap-mandatory [overscroll-behavior:contain] [scrollbar-width:none] shadow-[0_30px_60px_-34px_oklch(0_0_0/0.9)] focus-visible:outline-3 focus-visible:outline-tcar12-acc focus-visible:outline-offset-[3px]">
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-1/700/900" width="700" height="900" decoding="async" alt="Desert dunes at sunset" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Sahara, Morocco</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-2/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Glacier lagoon" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">J&ouml;kuls&aacute;rl&oacute;n, Iceland</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-3/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Terraced rice fields" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Bali, Indonesia</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-4/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Autumn forest path" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Kyoto, Japan</span></li>
  </ul>
</section>
@import "tailwindcss";

@theme {
  --color-tcar12-bg: #0e1513;
  --color-tcar12-bg: oklch(0.18 0.015 170);
  --color-tcar12-acc: #2dd4bf;
  --color-tcar12-acc: oklch(0.78 0.13 180);
  --font-tcar12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-12 {
  width: 100%;
  min-height: 100svh;
}

.tcar-12 ::selection {
  background: var(--color-tcar12-acc);
  color: #062120;
}

.tcar12-scroller::-webkit-scrollbar {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .tcar12-scroller {
    scroll-behavior: auto;
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tcar12-bg: #0e1513;
  --color-tcar12-bg: oklch(0.18 0.015 170);
  --color-tcar12-acc: #2dd4bf;
  --color-tcar12-acc: oklch(0.78 0.13 180);
  --font-tcar12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-12 {
  width: 100%;
  min-height: 100svh;
}

.tcar-12 ::selection {
  background: var(--color-tcar12-acc);
  color: #062120;
}

.tcar12-scroller::-webkit-scrollbar {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .tcar12-scroller {
    scroll-behavior: auto;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • Nothing about the vertical axis needs special handling in Tailwind: snap-y is the sibling of snap-x and snap-start is unchanged. The interesting utilities here are the two that have nothing to do with snapping — height and overscroll-behavior.
  • h-[min(560px,80svh)] uses svh rather than the original's vh: inside a mobile page the widget would otherwise be taller than the visible viewport whenever the URL bar is showing.
  • UI change from the CSS original: the caption scrim went from rgba(6,10,9,.85) to oklch(0.15 0.01 170 / 0.9). Several of the placeholder photographs are bright at the bottom edge, where the original caption dropped close to 4:1.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Vertical Image Carousel with Y-Axis Scroll Snap
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-vertical-image-carousel-scroll-snap/

A carousel that scrolls up and down instead of left to right — sidebar widgets, story feeds, creative portfolios. Tailwind ships snap-y as readily as snap-x, so the only genuinely vertical concern is giving the scroller a height and stopping its scroll from chaining to the page.
## HTML
```html
<section class="tcar-12 w-full min-h-svh grid place-items-center bg-tcar12-bg font-tcar12-sans px-5 py-10" aria-label="Vertical image carousel">
  <ul tabindex="0" role="group" aria-label="Gallery, scroll up and down" class="tcar12-scroller list-none w-[min(340px,100%)] h-[min(560px,80svh)] overflow-y-auto m-0 p-0 rounded-[20px] snap-y snap-mandatory [overscroll-behavior:contain] [scrollbar-width:none] shadow-[0_30px_60px_-34px_oklch(0_0_0/0.9)] focus-visible:outline-3 focus-visible:outline-tcar12-acc focus-visible:outline-offset-[3px]">
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-1/700/900" width="700" height="900" decoding="async" alt="Desert dunes at sunset" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Sahara, Morocco</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-2/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Glacier lagoon" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">J&ouml;kuls&aacute;rl&oacute;n, Iceland</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-3/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Terraced rice fields" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Bali, Indonesia</span></li>
    <li class="relative h-full snap-start overflow-hidden"><img src="https://picsum.photos/seed/vert-4/700/900" width="700" height="900" loading="lazy" decoding="async" alt="Autumn forest path" class="size-full object-cover block"><span class="absolute inset-x-0 bottom-0 px-5 pt-10 pb-5 text-[15px] font-semibold text-white bg-[linear-gradient(transparent,oklch(0.15_0.01_170/0.9))]">Kyoto, Japan</span></li>
  </ul>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tcar12-bg: #0e1513;
  --color-tcar12-bg: oklch(0.18 0.015 170);
  --color-tcar12-acc: #2dd4bf;
  --color-tcar12-acc: oklch(0.78 0.13 180);
  --font-tcar12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-12 {
  width: 100%;
  min-height: 100svh;
}

.tcar-12 ::selection {
  background: var(--color-tcar12-acc);
  color: #062120;
}

.tcar12-scroller::-webkit-scrollbar {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .tcar12-scroller {
    scroll-behavior: auto;
  }
}
```

How this works

Height is the requirement. A vertical snap container with no height has nothing to snap within; h-[min(560px,80svh)] gives it a window, and each frame's h-full makes exactly one land at a time.

Scroll chaining is the bug you would ship. Without [overscroll-behavior:contain], reaching the last frame hands the gesture to the page and the user scrolls past the widget mid-swipe. One arbitrary property prevents it.

Captions are text over a scrim. Real <span>s, legible and indexable, not burned into the photograph.

Make it yours

  • Window height: the h-[min(560px,80svh)] on the scroller.
  • Looser feel: snap-proximity instead of snap-mandatory.
  • Caption position: move it to top-0 and flip the gradient.
  • Focus ring colour: --color-tcar12-acc.

Gotchas — read before shipping

  • No explicit height means no vertical snapping at all — the single most common mistake in this pattern.
  • Each frame must be at least the scroller's height (h-full), or two frames share the window and neither snaps cleanly.
  • overscroll-behavior:contain, not none: none also disables pull-to-refresh on some platforms, which users notice.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Vertical scroll-snap is as well supported as horizontal and overscroll-behavior is Baseline; the floor quoted is Tailwind v4's generated sheet.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…