14 / 22
All 22 in 22 Tailwind CSS Image Carousel
- 01Tailwind E-commerce Product Gallery Carousel with Thumbnails
- 02Tailwind Shop the Look Multi-Item Card Slider with Scroll Snap
- 03Tailwind Infinite Logo Scroller Marquee with No JavaScript
- 04Tailwind Product Colour Variant Picker Slider
- 05Tailwind Full-Screen Hero Slideshow with Auto Cross-Fade
- 06Tailwind Testimonial Card Carousel with Star Ratings
- 07Tailwind Meet the Team Profile Slider with Circular Portraits
- 08Tailwind Portfolio Gallery Slider with Hover Caption Reveal
- 09Tailwind Netflix-Style Content Row Carousel with Hover Expand
- 10Tailwind Magazine Split-Screen Article Slider
- 11Tailwind Onboarding Slider with Native CSS Scroll-Marker Dots
- 12Tailwind Vertical Image Carousel with Y-Axis Scroll Snap
- 13Tailwind 3D Coverflow Carousel with Perspective Transforms
- 14Tailwind Cross-Fade Image Slideshow with Manual Controls
- 15Tailwind Dual Opposite-Direction Marquee Slider
- 16Tailwind Dot Pagination Carousel with Target and Has Selectors
- 17Tailwind Prev / Next Arrow Carousel with Native Scroll Buttons
- 18Tailwind Before / After Image Comparison Slider
- 19Tailwind Accordion Expanding Image Panel Carousel
- 20Tailwind Autoplay Slider with Keyframe Track and Progress Bars
- 21Tailwind Zoom-on-Hover Image Gallery Slider
- 22Tailwind Parallax Text-Over-Image Story Slider
Tailwind Cross-Fade Image Slideshow with Manual Controls
A manual slideshow that dissolves between photographs with pure opacity — no sliding, no reflow, no autoplay. The calm alternative to an auto-advancing hero, and the simplest possible demonstration of the group-has- state pattern.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcar-14 w-full min-h-svh grid place-items-center bg-tcar14-bg font-tcar14-sans px-5 py-10" aria-label="Photo slideshow">
<div class="group w-[min(620px,100%)]">
<input type="radio" name="tcar-14" id="tcar-14-1" class="sr-only" checked>
<input type="radio" name="tcar-14" id="tcar-14-2" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-3" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-4" class="sr-only">
<div class="relative aspect-[3/2] rounded-2xl overflow-hidden bg-tcar14-ph shadow-[0_26px_50px_-32px_oklch(0_0_0/0.9)]">
<img src="https://picsum.photos/seed/fade-1/900/600" width="900" height="600" fetchpriority="high" decoding="async" alt="Foggy harbour at dawn" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-2/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Neon-lit alley at night" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-3/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Snowy mountain village" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-3:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-4/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Field of lavender" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-4:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex gap-2.5 justify-center mt-4">
<label for="tcar-14-1" aria-label="Slide 1" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-1:checked]:bg-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-3 group-has-[#tcar-14-1:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-2" aria-label="Slide 2" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-2:checked]:bg-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-3 group-has-[#tcar-14-2:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-3" aria-label="Slide 3" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-3:checked]:bg-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-3 group-has-[#tcar-14-3:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-4" aria-label="Slide 4" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-4:checked]:bg-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-3 group-has-[#tcar-14-4:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-offset-[6px]"></label>
</div>
</div>
</section><section class="tcar-14 w-full min-h-svh grid place-items-center bg-tcar14-bg font-tcar14-sans px-5 py-10" aria-label="Photo slideshow">
<div class="group w-[min(620px,100%)]">
<input type="radio" name="tcar-14" id="tcar-14-1" class="sr-only" checked>
<input type="radio" name="tcar-14" id="tcar-14-2" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-3" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-4" class="sr-only">
<div class="relative aspect-[3/2] rounded-2xl overflow-hidden bg-tcar14-ph shadow-[0_26px_50px_-32px_oklch(0_0_0/0.9)]">
<img src="https://picsum.photos/seed/fade-1/900/600" width="900" height="600" fetchpriority="high" decoding="async" alt="Foggy harbour at dawn" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-2/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Neon-lit alley at night" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-3/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Snowy mountain village" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-3:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-4/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Field of lavender" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-4:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex gap-2.5 justify-center mt-4">
<label for="tcar-14-1" aria-label="Slide 1" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-1:checked]:bg-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-3 group-has-[#tcar-14-1:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-2" aria-label="Slide 2" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-2:checked]:bg-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-3 group-has-[#tcar-14-2:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-3" aria-label="Slide 3" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-3:checked]:bg-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-3 group-has-[#tcar-14-3:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-4" aria-label="Slide 4" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-4:checked]:bg-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-3 group-has-[#tcar-14-4:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-offset-[6px]"></label>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}<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">
<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.
- The purest example of the collection's state pattern: twelve CSS selectors reduce to four utilities, one per image, and there is nothing else going on to distract from the swap.
- The indicator's hit area is asymmetric —
before:-inset-x-1 before:-inset-y-[19px]— because a 34×5px bar needs height, not width, to reach 44px. This is the one control in the collection where abefore:hit area is safe: the bars sit on a 44px pitch (34px wide plus a 10px gap), so the boxes tile exactly instead of overlapping. Wherever the pitch is tighter than the target — demos 10, 16 and 22 — the 44px box belongs on the control itself, with the dot centred inside it. - UI change from the CSS original: the accent moved from
#0ea5e9tooklch(0.73 0.14 230)and the idle bar lightened. On the dark ground the original active bar and idle bar were close enough in luminance that the current slide was hard to identify at a glance.
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 Cross-Fade Image Slideshow with Manual Controls
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-crossfade-image-slideshow-manual/
A manual slideshow that dissolves between photographs with pure opacity — no sliding, no reflow, no autoplay. The calm alternative to an auto-advancing hero, and the simplest possible demonstration of the group-has- state pattern.
## HTML
```html
<section class="tcar-14 w-full min-h-svh grid place-items-center bg-tcar14-bg font-tcar14-sans px-5 py-10" aria-label="Photo slideshow">
<div class="group w-[min(620px,100%)]">
<input type="radio" name="tcar-14" id="tcar-14-1" class="sr-only" checked>
<input type="radio" name="tcar-14" id="tcar-14-2" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-3" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-4" class="sr-only">
<div class="relative aspect-[3/2] rounded-2xl overflow-hidden bg-tcar14-ph shadow-[0_26px_50px_-32px_oklch(0_0_0/0.9)]">
<img src="https://picsum.photos/seed/fade-1/900/600" width="900" height="600" fetchpriority="high" decoding="async" alt="Foggy harbour at dawn" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-2/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Neon-lit alley at night" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-3/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Snowy mountain village" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-3:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-4/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Field of lavender" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-4:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex gap-2.5 justify-center mt-4">
<label for="tcar-14-1" aria-label="Slide 1" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-1:checked]:bg-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-3 group-has-[#tcar-14-1:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-2" aria-label="Slide 2" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-2:checked]:bg-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-3 group-has-[#tcar-14-2:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-3" aria-label="Slide 3" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-3:checked]:bg-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-3 group-has-[#tcar-14-3:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-4" aria-label="Slide 4" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-4:checked]:bg-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-3 group-has-[#tcar-14-4:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-offset-[6px]"></label>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}
```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 Cross-Fade Image Slideshow with Manual Controls
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-crossfade-image-slideshow-manual/
A manual slideshow that dissolves between photographs with pure opacity — no sliding, no reflow, no autoplay. The calm alternative to an auto-advancing hero, and the simplest possible demonstration of the group-has- state pattern.
## HTML
```html
<section class="tcar-14 w-full min-h-svh grid place-items-center bg-tcar14-bg font-tcar14-sans px-5 py-10" aria-label="Photo slideshow">
<div class="group w-[min(620px,100%)]">
<input type="radio" name="tcar-14" id="tcar-14-1" class="sr-only" checked>
<input type="radio" name="tcar-14" id="tcar-14-2" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-3" class="sr-only">
<input type="radio" name="tcar-14" id="tcar-14-4" class="sr-only">
<div class="relative aspect-[3/2] rounded-2xl overflow-hidden bg-tcar14-ph shadow-[0_26px_50px_-32px_oklch(0_0_0/0.9)]">
<img src="https://picsum.photos/seed/fade-1/900/600" width="900" height="600" fetchpriority="high" decoding="async" alt="Foggy harbour at dawn" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-2/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Neon-lit alley at night" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-3/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Snowy mountain village" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-3:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/fade-4/900/600" width="900" height="600" loading="lazy" decoding="async" alt="Field of lavender" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-[800ms] ease-out group-has-[#tcar-14-4:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex gap-2.5 justify-center mt-4">
<label for="tcar-14-1" aria-label="Slide 1" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-1:checked]:bg-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-3 group-has-[#tcar-14-1:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-1:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-2" aria-label="Slide 2" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-2:checked]:bg-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-3 group-has-[#tcar-14-2:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-2:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-3" aria-label="Slide 3" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-3:checked]:bg-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-3 group-has-[#tcar-14-3:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-3:focus-visible]:outline-offset-[6px]"></label>
<label for="tcar-14-4" aria-label="Slide 4" class="relative w-[34px] h-[5px] rounded-full bg-tcar14-dot cursor-pointer transition-colors duration-250 before:content-[''] before:absolute before:-inset-x-1 before:-inset-y-[19px] hover:bg-tcar14-dothover group-has-[#tcar-14-4:checked]:bg-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-3 group-has-[#tcar-14-4:focus-visible]:outline-tcar14-acc group-has-[#tcar-14-4:focus-visible]:outline-offset-[6px]"></label>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar14-bg: #0f1720;
--color-tcar14-bg: oklch(0.2 0.02 250);
--color-tcar14-ph: #1b2733;
--color-tcar14-ph: oklch(0.26 0.02 250);
--color-tcar14-dot: #31404f;
--color-tcar14-dot: oklch(0.36 0.022 250);
--color-tcar14-dothover: #56697c;
--color-tcar14-dothover: oklch(0.52 0.03 250);
--color-tcar14-acc: #38b6f0;
--color-tcar14-acc: oklch(0.73 0.14 230);
--font-tcar14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-14 {
width: 100%;
min-height: 100svh;
}
.tcar-14 ::selection {
background: var(--color-tcar14-acc);
color: #062030;
}
```How this works
Only opacity moves. Stacked absolutely-positioned images, one visible. Nothing is translated and nothing is laid out, so the dissolve is free and CLS is structurally zero.
Manual by design. There is no timeline here at all — the user steps at their own pace with a click or an arrow key. For a gallery of unrelated photographs that is the accessible default; autoplay is demo 20's problem.
One slide must start checked. checked on the first radio is the difference between a slideshow and an empty grey box on first paint.
Make it yours
- Dissolve speed:
duration-[800ms]on the images. - Add a slide: one radio, one image, one bar, each with its own condition.
- Add a slow zoom:
group-has-[...]:scale-105alongside the opacity. - Prev/next labels instead of bars, pointed at specific radio ids.
Gotchas — read before shipping
- Exactly one radio carries
checkedat load. Zero means a blank frame; two is invalid markup and the browser picks for you. aspect-[3/2]on the frame, not on the images — the images are absolutely positioned and have no say in the box.- Do not add autoplay to this pattern without a pause control; a dissolve the user cannot stop is worse than one they must advance.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Opacity transitions are universal; :has() drives the state map (Chrome 105+, Safari 15.4+, Firefox 121+). The floor quoted is Tailwind v4's generated sheet.