What's included
A hand of cards spread out in a fan, in two files: a stylesheet and a script. No dependencies, no build step, no external requests — you copy them into a project and it works.
Each card moves aside, drops, tilts and shrinks in proportion to its distance from the chosen card. That arithmetic is done in CSS, from a single number: the index of the checked radio button, which
:has() publishes into a variable. That's why the fan is in place before the script arrives, and stays there if the script never comes.
Features
- The state lives in the document — a checked input, not a JavaScript variable. The CSS reads it, the keyboard moves through it, a form would submit it as it stands.
- Full keyboard support, without a line of code — they're native radio buttons: the arrow keys move from card to card, Tab steps into and out of the group, and the focus ring lands on the dot.
- Works without JavaScript — the fan is spread out, the cards are clickable, the dots respond. The script only adds dragging by finger and by mouse.
-
One variable for the scale — card, arc, tilt, corner radius, shadows and the five text sizes all derive from
--arc-font-size. -
The original spring, in CSS — the
linear()curve is sampled from the reference's spring, its 8.3% overshoot included. Measured path: 0.67 px of average difference over a 214 px run. -
It adapts to its container, not to the window
— the thresholds are
@containerqueries, so a fan dropped into a narrow column tightens up even on a large screen.
Dropping it into a page
Two files to load, then the markup:
<link rel="stylesheet" href="fan-carousel.css">
<script src="fan-carousel.js" defer></script>
<section class="fan-carousel" data-fan-carousel aria-label="Our plans">
<header class="fan-carousel__intro">
<p class="fan-carousel__eyebrow">Eyebrow</p>
<h2 class="fan-carousel__headline">The headline</h2>
</header>
<div class="fan-carousel__stage">
<article class="fan-carousel__card" role="group" aria-labelledby="t1">
<div class="fan-carousel__frame">
<img class="fan-carousel__image" src="…" alt="…">
<div class="fan-carousel__caption">
<div class="fan-carousel__text">
<h3 class="fan-carousel__title" id="t1">Title</h3>
<p class="fan-carousel__subtitle">Subtitle</p>
</div>
<span class="fan-carousel__tag">Label</span>
</div>
</div>
<label class="fan-carousel__hit" for="c1" aria-hidden="true"></label>
</article>
<!-- … -->
</div>
<div class="fan-carousel__dots" role="radiogroup" aria-label="Card to bring forward">
<input class="fan-carousel__radio" type="radio" name="deck" id="c1" checked>
<label class="fan-carousel__dot" for="c1"><span class="fan-carousel__sr">Title</span></label>
<!-- … -->
</div>
<div class="fan-carousel__probe" aria-hidden="true"></div>
</section>
One input per card, in the order of the cards: that's all the component asks for. The one checked at the start is the card that will be in front.
What you should know
- The fan is wider than its frame. That's the design: the cards stick out on both sides. The stage clips them horizontally and lets them fall away downwards, so the component never gives the host page a scrollbar — which the reference does, by 31 px at 810 and by 131 px at 390.
-
Twelve cards at most. The CSS writes one rule per rank; beyond that the fan isn't readable anyway. Each instance needs its own
nameandid. -
The arrow keys stay inside the component.
They act when a dot has focus, because they're the native arrow keys of a radio group. The reference listened for them on
window: they changed card even while you were typing into a field elsewhere on the page. -
No inline styles, no inline scripts. The component honours the site's strict security policy, checked by serving it under the header, and never uses
innerHTML. - The design comes from elsewhere. The proportions follow those of a commercial Framer component, measured on the published site at five widths. The code itself is original: nothing was taken from its implementation. The six photographs are those of the original demo, of unknown licence — replace them with your own.
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