22 CSS Image Carousels04 / 22

Pure CSSMIT licensed

Variant Picker Slider

Click a colour swatch to swap the product photo instantly — the interactive size/colour variant picker every apparel and furniture store needs. Pure CSS radio state cross-fades the hero image and marks the active swatch, no page reload and no script.

Published

Live Demo
Try it

The code

<section class="car-04" aria-label="Product colour variant picker">
  <div class="car-04__card">
    <input type="radio" name="car-04" id="car-04-1" class="car-04__radio" checked>
    <input type="radio" name="car-04" id="car-04-2" class="car-04__radio">
    <input type="radio" name="car-04" id="car-04-3" class="car-04__radio">
    <div class="car-04__stage">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" alt="Lounge chair in slate">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" alt="Lounge chair in ochre">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" alt="Lounge chair in moss">
    </div>
    <div class="car-04__foot">
      <p class="car-04__cap"><span class="car-04__label">Colour:</span> <span class="car-04__val car-04__val--1">Slate</span><span class="car-04__val car-04__val--2">Ochre</span><span class="car-04__val car-04__val--3">Moss</span></p>
      <div class="car-04__swatches">
        <label class="car-04__sw" for="car-04-1" style="background:#5b6470" aria-label="Slate"></label>
        <label class="car-04__sw" for="car-04-2" style="background:#c98a2b" aria-label="Ochre"></label>
        <label class="car-04__sw" for="car-04-3" style="background:#6f8256" aria-label="Moss"></label>
      </div>
    </div>
  </div>
</section>
.car-04,
.car-04 *,
.car-04 *::before,
.car-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.car-04 ::selection {
  background: #1f2430;
  color: #fff;
}

.car-04 {
  --accent: #1f2430;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: #f2f0ec;
  display: flex;
  align-items: center;
  justify-content: center;
}

.car-04__card {
  width: min(400px,100%);
  background: #fff;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 24px 50px -34px rgba(16,24,40,.6);
}

.car-04__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.car-04__stage {
  position: relative;
  aspect-ratio: 1/1;
  background: #e9e6e1;
}

.car-04__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .5s ease;
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__stage .car-04__img:nth-child(1),
.car-04__radio:nth-of-type(2):checked ~ .car-04__stage .car-04__img:nth-child(2),
.car-04__radio:nth-of-type(3):checked ~ .car-04__stage .car-04__img:nth-child(3) {
  opacity: 1;
}

.car-04__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 20px 20px;
}

.car-04__cap {
  font-size: 14px;
  color: #5a5f68;
}

.car-04__label {
  font-weight: 600;
  color: #20242c;
}

.car-04__val {
  display: none;
  font-weight: 600;
  color: #20242c;
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__foot .car-04__val--1,
.car-04__radio:nth-of-type(2):checked ~ .car-04__foot .car-04__val--2,
.car-04__radio:nth-of-type(3):checked ~ .car-04__foot .car-04__val--3 {
  display: inline;
}

.car-04__swatches {
  display: flex;
  gap: 10px;
}

.car-04__sw {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.14);
  transition: transform .2s,box-shadow .2s;
}

.car-04__sw:hover {
  transform: scale(1.08);
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__foot .car-04__sw:nth-child(1),
.car-04__radio:nth-of-type(2):checked ~ .car-04__foot .car-04__sw:nth-child(2),
.car-04__radio:nth-of-type(3):checked ~ .car-04__foot .car-04__sw:nth-child(3) {
  box-shadow: 0 0 0 2px var(--accent);
  transform: scale(1.12);
}

.car-04__radio:nth-of-type(1):focus-visible ~ .car-04__foot .car-04__sw:nth-child(1),
.car-04__radio:nth-of-type(2):focus-visible ~ .car-04__foot .car-04__sw:nth-child(2),
.car-04__radio:nth-of-type(3):focus-visible ~ .car-04__foot .car-04__sw:nth-child(3) {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .car-04__img,
    .car-04__sw {
    transition: none;
  }
}
/* No JavaScript — the radio group cross-fades the stacked variant photos and updates the swatch ring + colour caption via :checked selectors. */
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 Image Carousel 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: Variant Picker Slider
Source: https://codefronts.com/snippets/css-image-carousel/variant-picker-slider/

Click a colour swatch to swap the product photo instantly — the interactive size/colour variant picker every apparel and furniture store needs. Pure CSS radio state cross-fades the hero image and marks the active swatch, no page reload and no script.
## HTML
```html
<section class="car-04" aria-label="Product colour variant picker">
  <div class="car-04__card">
    <input type="radio" name="car-04" id="car-04-1" class="car-04__radio" checked>
    <input type="radio" name="car-04" id="car-04-2" class="car-04__radio">
    <input type="radio" name="car-04" id="car-04-3" class="car-04__radio">
    <div class="car-04__stage">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-slate/700/700" width="700" height="700" alt="Lounge chair in slate">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-ochre/700/700" width="700" height="700" alt="Lounge chair in ochre">
      <img class="car-04__img" src="https://picsum.photos/seed/chair-moss/700/700" width="700" height="700" alt="Lounge chair in moss">
    </div>
    <div class="car-04__foot">
      <p class="car-04__cap"><span class="car-04__label">Colour:</span> <span class="car-04__val car-04__val--1">Slate</span><span class="car-04__val car-04__val--2">Ochre</span><span class="car-04__val car-04__val--3">Moss</span></p>
      <div class="car-04__swatches">
        <label class="car-04__sw" for="car-04-1" style="background:#5b6470" aria-label="Slate"></label>
        <label class="car-04__sw" for="car-04-2" style="background:#c98a2b" aria-label="Ochre"></label>
        <label class="car-04__sw" for="car-04-3" style="background:#6f8256" aria-label="Moss"></label>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
.car-04,
.car-04 *,
.car-04 *::before,
.car-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.car-04 ::selection {
  background: #1f2430;
  color: #fff;
}

.car-04 {
  --accent: #1f2430;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: #f2f0ec;
  display: flex;
  align-items: center;
  justify-content: center;
}

.car-04__card {
  width: min(400px,100%);
  background: #fff;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 24px 50px -34px rgba(16,24,40,.6);
}

.car-04__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.car-04__stage {
  position: relative;
  aspect-ratio: 1/1;
  background: #e9e6e1;
}

.car-04__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .5s ease;
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__stage .car-04__img:nth-child(1),
.car-04__radio:nth-of-type(2):checked ~ .car-04__stage .car-04__img:nth-child(2),
.car-04__radio:nth-of-type(3):checked ~ .car-04__stage .car-04__img:nth-child(3) {
  opacity: 1;
}

.car-04__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 20px 20px;
}

.car-04__cap {
  font-size: 14px;
  color: #5a5f68;
}

.car-04__label {
  font-weight: 600;
  color: #20242c;
}

.car-04__val {
  display: none;
  font-weight: 600;
  color: #20242c;
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__foot .car-04__val--1,
.car-04__radio:nth-of-type(2):checked ~ .car-04__foot .car-04__val--2,
.car-04__radio:nth-of-type(3):checked ~ .car-04__foot .car-04__val--3 {
  display: inline;
}

.car-04__swatches {
  display: flex;
  gap: 10px;
}

.car-04__sw {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.14);
  transition: transform .2s,box-shadow .2s;
}

.car-04__sw:hover {
  transform: scale(1.08);
}

.car-04__radio:nth-of-type(1):checked ~ .car-04__foot .car-04__sw:nth-child(1),
.car-04__radio:nth-of-type(2):checked ~ .car-04__foot .car-04__sw:nth-child(2),
.car-04__radio:nth-of-type(3):checked ~ .car-04__foot .car-04__sw:nth-child(3) {
  box-shadow: 0 0 0 2px var(--accent);
  transform: scale(1.12);
}

.car-04__radio:nth-of-type(1):focus-visible ~ .car-04__foot .car-04__sw:nth-child(1),
.car-04__radio:nth-of-type(2):focus-visible ~ .car-04__foot .car-04__sw:nth-child(2),
.car-04__radio:nth-of-type(3):focus-visible ~ .car-04__foot .car-04__sw:nth-child(3) {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .car-04__img,
    .car-04__sw {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — the radio group cross-fades the stacked variant photos and updates the swatch ring + colour caption via :checked selectors. */
```

How this works

A hidden radio group (one shared name) records the chosen variant. Each swatch is a <label> whose background is the actual product colour, bound to its radio via for=. The variant photos are stacked in an aspect-ratio frame; the checked radio fades in the matching one with opacity while the others fade out — zero reflow.

The active swatch gets a ring and a scale bump through the :checked ~ chain, and :focus-visible mirrors that ring for keyboard users, who arrow-key between variants natively. The current colour name is echoed in a live-updating caption driven purely by sibling selectors.

Make it yours

  • Add a variant by adding a radio, a stage image, a swatch and a caption row, then extending the :nth-of-type map.
  • Set each swatch's real colour on its inline background.
  • Turn swatches into fabric thumbnails by dropping an <img> inside the label.
  • Recolour the active ring via --accent.

Gotchas — read before shipping

  • Keep a text label per colour (the caption) so the choice isn't communicated by colour alone — essential for colour-blind users.
  • Give the stage a fixed aspect-ratio to prevent CLS on image swap.
  • Ensure swatch labels have an accessible name (the for='d radio's label text or an aria-label).

Browser support

ChromeSafariFirefoxEdge
49+10+52+49+

Radio :checked sibling styling; supported in every modern browser.

Techniques used in this demo

Search CodeFronts

Loading…