webdevpuneet.com

Expandable Card — Free HTML CSS JS Shared-Layout Snippet

Expandable Card · Cards · Plain HTML, CSS & JS · Live preview

CategoryCards

Copy & paste

Source Code

<div class="xc-grid" id="xcGrid">
  <article class="xc-card" tabindex="0" data-color="#6366f1"><div class="xc-thumb">⚡</div><div class="xc-info"><h3>Edge Functions</h3><p>Run code close to users</p></div><button class="xc-go" aria-label="Expand">+</button>
    <template><h2>Edge Functions</h2><p>Deploy serverless functions to 30+ regions and execute them within milliseconds of every visitor. Cold starts are eliminated by keeping a warm pool at each edge node, so even the first request feels instant.</p><ul><li>Sub-50ms global latency</li><li>Zero cold starts</li><li>Streaming responses</li></ul><a class="xc-cta" href="#">Read the docs →</a></template>
  </article>
  <article class="xc-card" tabindex="0" data-color="#ec4899"><div class="xc-thumb">🛡</div><div class="xc-info"><h3>Vault</h3><p>Encrypted secrets store</p></div><button class="xc-go" aria-label="Expand">+</button>
    <template><h2>Vault</h2><p>Store API keys, tokens, and certificates with envelope encryption and granular access policies. Secrets are decrypted only in memory at runtime and never written to disk or logs.</p><ul><li>Zero-knowledge storage</li><li>Audit log of every read</li><li>Automatic rotation</li></ul><a class="xc-cta" href="#">Read the docs →</a></template>
  </article>
  <article class="xc-card" tabindex="0" data-color="#34d399"><div class="xc-thumb">∞</div><div class="xc-info"><h3>Autoscale</h3><p>Zero to millions</p></div><button class="xc-go" aria-label="Expand">+</button>
    <template><h2>Autoscale</h2><p>Capacity follows demand automatically — scale from zero to millions of concurrent requests and back without provisioning a thing. You pay only for what executes, billed by the millisecond.</p><ul><li>Scale-to-zero idle cost</li><li>Per-millisecond billing</li><li>No config required</li></ul><a class="xc-cta" href="#">Read the docs →</a></template>
  </article>
</div>
<div class="xc-overlay" id="xcOverlay" aria-hidden="true"></div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

FLIP shared-layout
Animates from the card's rect to a modal.
Template content
Full detail lives in an unrendered template.
Source-card hidden
No duplicate; the spot is preserved for collapse.
Blurred backdrop
backdrop-filter dims the page behind.
Full dismissal
Close button, overlay click, and Escape.
Open guard
Only one card expands at a time.
Keyboard accessible
Enter, Space, and Escape supported.
Per-card theming
A --c accent flows through the modal.

About this UI Snippet

Expandable Card — FLIP Animation From List Item to Modal

Screenshot of the Expandable Card snippet rendered live

The expandable card is the polished interaction — popularized by the macOS App Store and shared-layout animations — where clicking a compact list card makes it grow smoothly from its spot into a centered modal showing full content, then shrink back into place when dismissed. This snippet implements that shared-element transition with plain HTML, CSS, and vanilla JavaScript using the FLIP technique.

The FLIP technique

FLIP stands for First, Last, Invert, Play, and it's how you animate between two layouts smoothly. Here, when a card is clicked, the code reads its current position and size with getBoundingClientRect (the First state), creates a fixed-position clone placed exactly over it, then on the next frame sets the clone's target position and size to a centered modal (the Last state). Because the clone has a CSS transition: all, the browser animates from First to Last automatically — the card appears to lift off the page and expand into a dialog. On close, the same process runs in reverse: the clone animates back to the original card's current rect, then is removed.

Hidden content in a template

Each card carries its expanded content inside a <template> element, which the browser never renders. On expand, that template's HTML is injected into the clone, so the compact card stays lightweight while the full description, feature list, and call-to-action only exist when needed. This keeps the list clean and avoids rendering heavy content for cards nobody has opened.

Source card hidden during expansion

When the clone takes over, the original card is set visibility: hidden so you don't see a duplicate behind the modal — but it keeps its space in the layout, so on collapse the clone has a correct rect to animate back into. Restoring visibility after the collapse transition completes hands the spot back to the real card seamlessly.

A blurred backdrop and full dismissal

Opening shows a blurred, dimmed overlay (backdrop-filter: blur) that transitions opacity and visibility together so it's non-interactive when closed. The modal can be dismissed by its close button, by clicking the overlay, or with the Escape key — all routed to the same collapse() function. An active guard prevents opening a second card while one is already expanded.

Keyboard accessible

Cards have tabindex="0" and respond to Enter and Space to expand, and Escape to close, so the whole interaction works without a mouse. Each card's accent color flows through a --c variable into the thumbnail, the expand button on hover, the feature checkmarks, and the CTA, so the modal is themed per card automatically.

Customizing it

Adjust the modal's target width and vertical position, change the transition duration and easing, restyle the expanded content, or theme each card via its data-color. Swap the emoji thumbnails for icons or images. Pair it with a bento grid of features or an Instagram gallery for a rich, interactive page.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA list of compact feature cards renders.
  2. 2
    Click a cardIt grows smoothly from its spot into a centered modal.
  3. 3
    Read the detailFull content from a hidden template appears in the modal.
  4. 4
    Dismiss itClick the backdrop, the X, or press Escape to shrink it back.
  5. 5
    Use the keyboardTab to a card and press Enter to expand it.
  6. 6
    Theme each cardSet each card's data-color accent.

Real-world uses

Common Use Cases

Feature detail expansion
Grow a card from its place in a feature cards layout into a centred modal, using a FLIP transition from the card's own rectangle.
App and integration grids
Pair with integration cards in an app grid, keeping full details in an unrendered template until a card is opened.
Pricing plan specifics
Open plan details beside a pricing card, hiding the source card so its spot is preserved for the collapse.
Case study thumbnails
Grow a thumbnail into a full case study, with a blurred backdrop dimming the page behind through backdrop-filter.
Settings row detail
Expand a row from a settings panel into a larger view, as a reference for shared-element transitions.
Related: Nutrition Label
See the Nutrition Label for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

FLIP — First, Last, Invert, Play — animates between two layouts. The code reads the card's current rect (First), creates a fixed clone over it, then on the next frame sets the clone's centered-modal position and size (Last). A CSS transition: all on the clone plays the animation from First to Last automatically, so the card appears to expand into a dialog and shrink back on close.

A <template> element is never rendered by the browser, so each card's full description, feature list, and CTA add no weight until needed. On expand, the template's HTML is injected into the clone. This keeps the compact list lightweight and avoids rendering heavy content for cards the user never opens.

When the clone takes over, the source card is set visibility: hidden so there's no duplicate behind the modal — but it keeps its layout space, so on collapse the clone has a valid rect to animate back into. Restoring its visibility after the collapse transition hands the spot back seamlessly.

All dismissal paths route to one collapse() function: the close button, clicking the blurred overlay, and pressing Escape. collapse() animates the clone back to the source card's current rect, fades the overlay, then removes the clone and restores the card. An active guard also prevents opening another card while one is expanded.

You can reproduce FLIP manually with refs and getBoundingClientRect in a layout effect, or use a shared-layout library (Framer Motion's layoutId in React, AutoAnimate or the View Transitions API in Vue/Angular). Keep an expanded-id in state, render the modal content conditionally, and animate between the card and modal rects. The backdrop and dismissal logic port directly.