You Might Also Like
Expandable Card — Free HTML CSS JS Shared-Layout Snippet
Expandable Card · Cards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Expandable Card — FLIP Animation From List Item to Modal

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.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work through the FLIP math from scratch. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly what the First, Last, Invert, Play sequence is doing in the expand function, and why the clone's position is set with getBoundingClientRect before it's appended rather than after. The same assistant can help optimize it — ask whether creating and destroying a whole new DOM clone on every expand and collapse is efficient enough for a grid with dozens of cards, or whether a single reusable modal element with repositioned content would perform better. It's also useful for extending the interaction: ask it to support swiping between expanded cards without fully collapsing first, add a shared image element that also FLIPs alongside the text content, or persist the expanded card's id in the URL so a direct link opens straight into the expanded view. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an expandable card grid in plain HTML, CSS, and JavaScript using the FLIP animation technique (First, Last, Invert, Play) — no animation library.
Requirements:
- A vertical list of compact cards, each with a thumbnail, a title, and a short description, and each card carrying its full expanded content (a longer description, a feature list, and a link) inside a hidden template element so that content adds no weight to the initial page render.
- On click, read the clicked card's current bounding rectangle with getBoundingClientRect, then create a new fixed-position element sized and positioned to exactly match that rectangle (the First state), inject the card's template content into it, and append it to the document body.
- On the next animation frame (not immediately), change that fixed element's position, size, and opacity to a centered modal layout (the Last state), relying on a CSS transition on the element to animate smoothly between the two states without any manual keyframe interpolation.
- Hide the original source card with visibility hidden (not display none) while its clone is expanded, so the card keeps its layout space and provides a valid rectangle to animate back into when collapsing.
- Implement collapse as the exact reverse: read the source card's current rectangle, animate the expanded clone back down to that size and position with opacity fading to zero, then after the transition duration completes, remove the clone from the DOM and restore the source card's visibility.
- Show a blurred, dimmed backdrop behind the expanded card that fades in and out, and support closing via a close button, clicking the backdrop, and pressing Escape, all routed through the same collapse function.
- Ensure only one card can be expanded at a time by tracking the currently active expansion in a shared variable and ignoring new expand attempts while one is already open.
- Make the whole interaction keyboard accessible: cards must be focusable and expand on Enter or Space.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA list of compact feature cards renders.
- 2Click a cardIt grows smoothly from its spot into a centered modal.
- 3Read the detailFull content from a hidden template appears in the modal.
- 4Dismiss itClick the backdrop, the X, or press Escape to shrink it back.
- 5Use the keyboardTab to a card and press Enter to expand it.
- 6Theme each cardSet each card's data-color accent.
Real-world uses
Common Use Cases
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.