04 / 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 Product Colour Variant Picker Slider
Click a swatch to swap the product photo — the apparel and furniture variant picker. Three elements change per choice (photo, swatch ring, colour caption) and in Tailwind all three carry the same group-has-[#id:checked]: condition, which is the clearest argument for the 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-04 w-full min-h-svh grid place-items-center bg-tcar04-bg font-tcar04-sans px-5 py-10" aria-label="Product colour variant picker">
<div class="group w-[min(400px,100%)] bg-white rounded-[18px] overflow-hidden shadow-[0_24px_50px_-34px_oklch(0.3_0.03_265/0.6)]">
<input type="radio" name="tcar-04" id="tcar-04-1" class="sr-only" checked>
<input type="radio" name="tcar-04" id="tcar-04-2" class="sr-only">
<input type="radio" name="tcar-04" id="tcar-04-3" class="sr-only">
<div class="relative aspect-square bg-tcar04-ph">
<img src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" fetchpriority="high" decoding="async" alt="Lounge chair in slate" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in ochre" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in moss" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-3:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex items-center justify-between gap-3.5 px-5 pt-4 pb-5">
<p class="text-sm text-tcar04-mute m-0"><span class="font-semibold text-tcar04-ink">Colour:</span>
<span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-1:checked]:inline">Slate</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-2:checked]:inline">Ochre</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-3:checked]:inline">Moss</span>
</p>
<div class="flex gap-4">
<label for="tcar-04-1" aria-label="Slate" style="background:#5b6470" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-1:checked]:scale-[1.12] group-has-[#tcar-04-1:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-1:focus-visible]:outline-3 group-has-[#tcar-04-1:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-1:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-2" aria-label="Ochre" style="background:#c98a2b" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-2:checked]:scale-[1.12] group-has-[#tcar-04-2:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-2:focus-visible]:outline-3 group-has-[#tcar-04-2:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-2:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-3" aria-label="Moss" style="background:#6f8256" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-3:checked]:scale-[1.12] group-has-[#tcar-04-3:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-3:focus-visible]:outline-3 group-has-[#tcar-04-3:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-3:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
</div>
</div>
</div>
</section><section class="tcar-04 w-full min-h-svh grid place-items-center bg-tcar04-bg font-tcar04-sans px-5 py-10" aria-label="Product colour variant picker">
<div class="group w-[min(400px,100%)] bg-white rounded-[18px] overflow-hidden shadow-[0_24px_50px_-34px_oklch(0.3_0.03_265/0.6)]">
<input type="radio" name="tcar-04" id="tcar-04-1" class="sr-only" checked>
<input type="radio" name="tcar-04" id="tcar-04-2" class="sr-only">
<input type="radio" name="tcar-04" id="tcar-04-3" class="sr-only">
<div class="relative aspect-square bg-tcar04-ph">
<img src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" fetchpriority="high" decoding="async" alt="Lounge chair in slate" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in ochre" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in moss" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-3:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex items-center justify-between gap-3.5 px-5 pt-4 pb-5">
<p class="text-sm text-tcar04-mute m-0"><span class="font-semibold text-tcar04-ink">Colour:</span>
<span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-1:checked]:inline">Slate</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-2:checked]:inline">Ochre</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-3:checked]:inline">Moss</span>
</p>
<div class="flex gap-4">
<label for="tcar-04-1" aria-label="Slate" style="background:#5b6470" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-1:checked]:scale-[1.12] group-has-[#tcar-04-1:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-1:focus-visible]:outline-3 group-has-[#tcar-04-1:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-1:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-2" aria-label="Ochre" style="background:#c98a2b" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-2:checked]:scale-[1.12] group-has-[#tcar-04-2:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-2:focus-visible]:outline-3 group-has-[#tcar-04-2:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-2:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-3" aria-label="Moss" style="background:#6f8256" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-3:checked]:scale-[1.12] group-has-[#tcar-04-3:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-3:focus-visible]:outline-3 group-has-[#tcar-04-3:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-3:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}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-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}<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.
- Nine
:checked ~selectors become threegroup-has-[...]conditions, each written once and reused verbatim at three different depths. This demo is the clearest evidence for the pattern in the collection. - Swatch colours stay as inline
stylerather than@themeentries: a product colourway is content, not a design token, and minting--color-tcar04-ochrewould imply the theme owns it. - UI change from the CSS original: each 28px swatch gained a
before:-inset-2invisible hit area, taking the touch target to 44×44px and satisfying WCAG 2.5.5 — the original's swatches were 28px of tappable area on a phone.
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 Product Colour Variant Picker Slider
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-product-colour-variant-picker-slider/
Click a swatch to swap the product photo — the apparel and furniture variant picker. Three elements change per choice (photo, swatch ring, colour caption) and in Tailwind all three carry the same group-has-[#id:checked]: condition, which is the clearest argument for the pattern.
## HTML
```html
<section class="tcar-04 w-full min-h-svh grid place-items-center bg-tcar04-bg font-tcar04-sans px-5 py-10" aria-label="Product colour variant picker">
<div class="group w-[min(400px,100%)] bg-white rounded-[18px] overflow-hidden shadow-[0_24px_50px_-34px_oklch(0.3_0.03_265/0.6)]">
<input type="radio" name="tcar-04" id="tcar-04-1" class="sr-only" checked>
<input type="radio" name="tcar-04" id="tcar-04-2" class="sr-only">
<input type="radio" name="tcar-04" id="tcar-04-3" class="sr-only">
<div class="relative aspect-square bg-tcar04-ph">
<img src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" fetchpriority="high" decoding="async" alt="Lounge chair in slate" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in ochre" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in moss" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-3:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex items-center justify-between gap-3.5 px-5 pt-4 pb-5">
<p class="text-sm text-tcar04-mute m-0"><span class="font-semibold text-tcar04-ink">Colour:</span>
<span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-1:checked]:inline">Slate</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-2:checked]:inline">Ochre</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-3:checked]:inline">Moss</span>
</p>
<div class="flex gap-4">
<label for="tcar-04-1" aria-label="Slate" style="background:#5b6470" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-1:checked]:scale-[1.12] group-has-[#tcar-04-1:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-1:focus-visible]:outline-3 group-has-[#tcar-04-1:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-1:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-2" aria-label="Ochre" style="background:#c98a2b" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-2:checked]:scale-[1.12] group-has-[#tcar-04-2:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-2:focus-visible]:outline-3 group-has-[#tcar-04-2:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-2:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-3" aria-label="Moss" style="background:#6f8256" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-3:checked]:scale-[1.12] group-has-[#tcar-04-3:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-3:focus-visible]:outline-3 group-has-[#tcar-04-3:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-3:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}
```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 Product Colour Variant Picker Slider
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-product-colour-variant-picker-slider/
Click a swatch to swap the product photo — the apparel and furniture variant picker. Three elements change per choice (photo, swatch ring, colour caption) and in Tailwind all three carry the same group-has-[#id:checked]: condition, which is the clearest argument for the pattern.
## HTML
```html
<section class="tcar-04 w-full min-h-svh grid place-items-center bg-tcar04-bg font-tcar04-sans px-5 py-10" aria-label="Product colour variant picker">
<div class="group w-[min(400px,100%)] bg-white rounded-[18px] overflow-hidden shadow-[0_24px_50px_-34px_oklch(0.3_0.03_265/0.6)]">
<input type="radio" name="tcar-04" id="tcar-04-1" class="sr-only" checked>
<input type="radio" name="tcar-04" id="tcar-04-2" class="sr-only">
<input type="radio" name="tcar-04" id="tcar-04-3" class="sr-only">
<div class="relative aspect-square bg-tcar04-ph">
<img src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" fetchpriority="high" decoding="async" alt="Lounge chair in slate" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-1:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in ochre" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-2:checked]:opacity-100 motion-reduce:transition-none">
<img src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" loading="lazy" decoding="async" alt="Lounge chair in moss" class="absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-500 ease-out group-has-[#tcar-04-3:checked]:opacity-100 motion-reduce:transition-none">
</div>
<div class="flex items-center justify-between gap-3.5 px-5 pt-4 pb-5">
<p class="text-sm text-tcar04-mute m-0"><span class="font-semibold text-tcar04-ink">Colour:</span>
<span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-1:checked]:inline">Slate</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-2:checked]:inline">Ochre</span><span class="hidden font-semibold text-tcar04-ink group-has-[#tcar-04-3:checked]:inline">Moss</span>
</p>
<div class="flex gap-4">
<label for="tcar-04-1" aria-label="Slate" style="background:#5b6470" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-1:checked]:scale-[1.12] group-has-[#tcar-04-1:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-1:focus-visible]:outline-3 group-has-[#tcar-04-1:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-1:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-2" aria-label="Ochre" style="background:#c98a2b" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-2:checked]:scale-[1.12] group-has-[#tcar-04-2:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-2:focus-visible]:outline-3 group-has-[#tcar-04-2:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-2:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
<label for="tcar-04-3" aria-label="Moss" style="background:#6f8256" class="relative size-7 rounded-full cursor-pointer border-2 border-white shadow-[0_0_0_1px_oklch(0_0_0/0.14)] transition-[scale,box-shadow] duration-200 before:content-[''] before:absolute before:-inset-2 before:rounded-full hover:scale-[1.08] group-has-[#tcar-04-3:checked]:scale-[1.12] group-has-[#tcar-04-3:checked]:shadow-[0_0_0_2px_var(--color-tcar04-acc)] group-has-[#tcar-04-3:focus-visible]:outline-3 group-has-[#tcar-04-3:focus-visible]:outline-tcar04-acc group-has-[#tcar-04-3:focus-visible]:outline-offset-[3px] motion-reduce:transition-none"></label>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar04-bg: #f2f0ec;
--color-tcar04-bg: oklch(0.95 0.006 85);
--color-tcar04-ph: #e9e6e1;
--color-tcar04-ph: oklch(0.93 0.006 85);
--color-tcar04-ink: #20242c;
--color-tcar04-ink: oklch(0.26 0.012 265);
--color-tcar04-mute: #5a5f68;
--color-tcar04-mute: oklch(0.48 0.012 265);
--color-tcar04-acc: #1f2430;
--color-tcar04-acc: oklch(0.27 0.02 265);
--font-tcar04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-04 {
width: 100%;
min-height: 100svh;
}
.tcar-04 ::selection {
background: var(--color-tcar04-acc);
color: #fff;
}
```How this works
One condition, every layer. Choosing ochre must change three things at three nesting depths. The CSS version writes nine selectors; Tailwind writes group-has-[#tcar-04-2:checked]: three times, each on the element it affects.
The caption is the accessibility argument. Each colour name is a <span class="hidden group-has-[#tcar-04-2:checked]:inline">, so the selected variant is stated in words. A colour-blind user reads "Colour: Ochre" rather than inferring it from a ring.
The swatch grew a hit area. A 28px circle is a fine visual and a poor touch target; before:-inset-2 extends the tappable box to 44px without moving a pixel.
Make it yours
- Add a variant: one radio, one stage image, one swatch, one caption word, each with its own
group-has-[#tcar-04-4:checked]:. - Real colours stay inline
style="background:#c98a2b"— product colours are data, not theme tokens. - Fabric thumbnails instead of flat colour: drop an
<img>inside the label. - Ring colour:
--color-tcar04-acc.
Gotchas — read before shipping
- Every swatch label needs an
aria-label, or its accessible name is the empty string and the radio is unusable by voice control. - Keep the caption. A ring alone fails WCAG 1.4.1 (Use of Colour) for the one group of users most affected by a colour picker.
aspect-squareon the stage, always — variant photos swap on click and an unreserved box shifts the whole page.- Whenever you inflate a control with
before:-inset-*, check the gap: the hit area must be no wider than the pitch (visible size + gap), or adjacent targets overlap and one swatch steals its neighbour's clicks.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() drives the state map (Chrome 105+, Safari 15.4+, Firefox 121+); the floor quoted is Tailwind v4's generated sheet.