← The library

Arc carousel

A carousel of images on a circular arc

Members only

What's included

A carousel of images set on the rim of a giant wheel, in two files: a stylesheet and a script. No dependencies, no build step, no external requests — fonts and images live in the component's folder.

The card at the top grows, lifts and speaks up; the others tilt, shrink and fade as they travel down the arc. All of it derives from a single number, --wcar-index, which is the selected card. Because that number is declared with @property, animating it from one card to the next turns the whole wheel, resizes it, reorders the layers and moves the text — in a single movement, since everything descends from it.

Features

  • Works without JavaScript — each card is the <label> of a radio button. Clicking a card selects it, the arrow keys move through the group, and the wheel turns: it's the CSS that reads the selection with :has().
  • Full keyboard support — the radio group brings its native traversal; the focus ring is painted on the card, not on the control. Focus follows the navigation arrows when the caption changes.
  • Grab, spin, release — the wheel can be grabbed anywhere: on the background, on a card, on the text. Beyond five pixels the press becomes a drag; below that, it stays a click that selects. The wheel then settles on the nearest card with a spring curve, sampled from the original.
  • The count is read from the markup — two cards or twelve, the arc spreads itself out. Nothing to declare, nothing to recalculate.
  • One variable for the scale — radius, cards, text, dots and lift all derive from --wcar-font-size; the arc itself carries on following the width of its container.
  • Breakpoints on the container — the arc opens less and the cards take up more room in a narrow column, even if the window itself is wide.

Dropping it into a page

Two files to load, then one card per image:

<link rel="stylesheet" href="arc-carousel.css">
<script src="arc-carousel.js" defer></script>
<section class="arc-carousel" data-arc-carousel aria-label="Moods">
  <div class="arc-carousel__stage">
    <div class="arc-carousel__halo"></div>
    <div class="arc-carousel__track" role="radiogroup"
         aria-label="Choose an image">
      <div class="arc-carousel__item">
        <label class="arc-carousel__card">
          <input class="arc-carousel__radio" type="radio"
                 name="roue" checked>
          <img class="arc-carousel__image" src="medias/salon.webp" alt="">
          <span class="arc-carousel__veil"></span>
          <span class="arc-carousel__name">Soft minimalism</span>
        </label>
        <div class="arc-carousel__caption">
          <div class="arc-carousel__panel">
            <div class="arc-carousel__headline">
              <button class="arc-carousel__arrow" type="button"
                      data-wcar-prev aria-label="Previous">…</button>
              <p class="arc-carousel__title">Soft minimalism</p>
              <button class="arc-carousel__arrow" type="button"
                      data-wcar-next aria-label="Next image">…</button>
            </div>
            <p class="arc-carousel__text">Small, carefully chosen spaces.</p>
            <a class="arc-carousel__cta" href="#">Explore</a>
          </div>
        </div>
      </div>
      <!-- … the other cards … -->
    </div>
    <div class="arc-carousel__dots" aria-hidden="true">
      <span class="arc-carousel__dot"></span>
    </div>
    <span class="arc-carousel__probe" aria-hidden="true"></span>
    <p class="arc-carousel__status" role="status" aria-live="polite"></p>
  </div>
</section>

A single checked in the group, a different name per carousel on the page, and that's all: the component counts the cards itself.

What you should know

  • The wheel takes the height you give it. By default 100svh; the position of the text block is worked out from that height, so changing it moves the text, never the arc. That's --wcar-height.
  • Half the wheel is outside the frame. That's the design: the cards at either end leave through the sides. The frame is set to overflow: clip — so it can't scroll, and focus landing on an off-frame card doesn't shift the layout.
  • Without the script, the two arrows disappear. They would ask for “the next card”, which a stylesheet has no way of expressing. Everything else — selecting by click, keyboard traversal, rotation, captions — carries on working.
  • No inline styles, no inline scripts. The component honours the site's strict security policy, and never uses innerHTML. The script writes its position through style.setProperty(), which the policy allows.
  • The design comes from elsewhere. The proportions follow those of a published Framer component, measured in the browser on the live site at five widths. The code itself is original: the original is a React carousel that recomputes ten positions every frame, this one is a stylesheet that derives everything from one number.

Take it, open it, break it.

This component is free. The full library — the templates and the other components — comes with the subscription, alongside the course.

Immediate access · No commitment · Cancel in one click