013

Elastic Stack Cards

£5

Stacked cards that spring open into a fan. Three fan shapes, opened by hover, focus, tap or an external button, with click-to-front card selection.

ScrollTrigger Lenis intermediate
5 more details
hover-effectstaggerscroll-revealscrub3d-transforms
Elastic Stack Cards - GSAP animation effect preview

This demo reads better at your own screen size than in the frame below:

About this effect

A capability deck that rests as a tight pile and springs into a full-width fan on hover, focus or tap. Three fan shapes ship with it (an even spread, a curved arc, and a diagonal cascade), switchable at runtime from any button on the page, and the deck can be held open by an external toggle instead of a hover. Clicking a fanned card lifts it clear of its neighbours and flips it to the accent colour. Spacing is measured from the container at fan time, so the same markup fills a wide desktop stage and closes to a hand of cards on a phone. An optional scroll-scrubbed variant drives the identical layout from scroll position instead.

What's included

8 items
  • Three fan shapes (spread, arc and cascade) set with data-fan-mode and switchable at runtime
  • External controls: [data-fan-mode-control] buttons pick the shape, [data-fan-toggle] holds the deck open
  • Spring physics via GSAP elastic easing, with per-card stagger out from the centre
  • Click-to-front selection that lifts the card clear and flips it to the accent colour
  • Fan width measured from the container, so end cards never overflow their stage
  • Optional scroll-scrubbed variant that fans the same markup from scroll position
  • Hover, keyboard focus and touch tap all open the deck
  • Reduced-motion fallback lays every card out as a plain, readable grid

Perfect for

5 use cases
  • Service or capability decks on agency and studio sites
  • Process steps that fan out from a single pile
  • Pricing or plan cards that stay compact until someone looks at them
  • Portfolio project stacks with a tactile, physical reveal
  • Feature highlights that need to sit in a fixed-height section

How it works

1 section

Cards are absolutely positioned inside a flex-centred container and seated in a resting pile with small offsets, tilt and scale. On hover, focus, tap or an external toggle, the script measures the container and the card, subtracts an allowance for how much wider a tilted card is than its box, and hands the result to the selected shape function, which returns an x, y, rotation and scale per card. Each card animates there with elastic.out and a stagger that grows from the centre of the deck. Clicking a card raises its z-index and tweens it above its fanned position. The scroll variant feeds the same shape functions into a ScrollTrigger timeline with scrub instead of tweening on pointer events.

Plugins ScrollTrigger
Difficulty Intermediate
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Elastic Stack Cards opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£5 Standard license, unlimited projects

Browse free effects

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add before closing </body> tag:

Code snippet omitted: it ships with the download.

3. Add to your <body>:

Code snippet omitted: it ships with the download.

Buttons elsewhere on the page can drive it by pointing data-target at the deck's id:

Code snippet omitted: it ships with the download.

Using It With Your Own Design

What the effect requires of your markup: one .elastic-stack container with data-elastic-hover, and .elastic-card children. Nothing inside a card is required — the script never reads or writes card content, only transforms. The container must be a positioned, centred box with the cards position: absolute inside it; everything else about how a card looks is yours.

What is only the demo's CSS. In style.css, everything under "SHOWCASE SHELL" — .showcase-shell, .showcase-toolbar, .showcase-prompt, .controls, .control-group, .control-btn, .toggle-btn — is the demo page's dressing, as are the tokens the demo's dark room and chalk card faces are built from and the Mona Sans font link in index.html. Delete all of it. So is the card's own look: .card-index, .card-title, .card-desc, .card-tag, .card-mark and the .mark--* glyphs. What the effect actually needs is the .elastic-stack and .elastic-card blocks, plus .elastic-card--active if you want a selected state.

The non-obvious CSS the effect depends on:

  • .elastic-card must be position: absolute inside a .elastic-stack that centres them (the demo uses flex with justify-content: center). The fan is computed as offsets from that shared centre, so if the cards flow normally they will fan from wherever they happen to sit.
  • The fan width is measured from .elastic-stack, not the viewport. Give the container the full width you want the fan to use. A container narrower than the cards produces a pile, not a fan, and nothing errors.
  • Card copy needs a max-width narrower than the strip a neighbour leaves showing. The demo sets --card-strip for this. Without it, long words run under the next card and read as a rendering fault. The strip is roughly container width ÷ card count.
  • transform-origin: center bottom is what makes the tilt read as cards pivoting in a hand rather than spinning about their middles.
  • Nothing is hidden at rest. With JavaScript off, the :nth-child rules keep a static, plausible pile, so the section never renders blank. If you change the card count, update those rules to match.
  • Reduced motion unhides everything: the shell scrolls, the stack becomes a wrapped flex grid and the absolute positioning is dropped, so every card is readable with no animation.

Options

On the .elastic-stack

Attribute Values Default Description
data-elastic-hover Enables the hover / focus / tap fan
data-elastic-scroll Enables the scroll-scrubbed variant instead (needs ScrollTrigger)
data-fan-mode spread, arc, cascade spread Which shape the deck fans into
id any Required only if external controls target this deck

Fan shapes

Mode Shape
spread Even horizontal fan, ends lifted, gentle outward tilt
arc Same width, ends dropped along a curve with a stronger tilt and slight scale-down
cascade Same width, laddered diagonally from top-left to bottom-right at a constant tilt

All three use the same horizontal travel, so the amount of each card left showing does not change when you switch shapes.

External controls

Attribute Element Description
data-fan-mode-control <button> Sets data-fan-mode; gets .active and aria-pressed managed for you
data-fan-toggle <button> Holds the deck open or closed regardless of hover; aria-pressed managed
data-fan-toggle-label any child Its text is swapped between "Fan out" and "Collapse"
data-target either The id of the deck the button drives

Card count

3–8 cards. More cards means less of each one shows, so shorten the copy as the count goes up.

Accessibility

  • Reduced motion: no fan at all. Every card is laid out in a plain wrapped grid, fully readable, with no transforms and no listeners attached
  • Keyboard: focusing the stack or any card fans the deck; Enter or Space on a card selects it. Focus leaving the stack closes it again unless a toggle is holding it open
  • Touch: tapping the deck toggles the fan. Card taps do not select, so a tap never traps you with a lifted card
  • Screen readers: give the stack role="group" and an aria-label that says what the deck is and how it opens. The control buttons carry their own aria-pressed state

Browser Support

Modern browsers (ES6+). Not compatible with IE11.

Dependencies

Required:

Optional:

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

The Vault library, plus future additions to the library.