Card Morph
A compact card (avatar dot, name, role) that widens from 200 to 300 px with a bounce on click and rolls out a paragraph and three gradient figures; a second click folds it back. The content shows up inside the same card, without leaving the page: the header is a real button that says whether the card is open, and folded details are hidden from screen readers.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .card-morph card is 200 px wide, with 20 px of padding, a translucent violet background and 16 px corners. Its .card-header header is a <button> holding the 40 px avatar dot (initials on a violet-pink gradient), the name and the role. Below, .card-details waits folded: max-height: 0, opacity: 0. On click, the script toggles .expanded on the card: it goes to 300 px with transition: .6s cubic-bezier(.68, -.55, .27, 1.55), a bounce that makes it shrink slightly and then overshoot its width; the details open (max-height up to 200 px over 0.6 s, opacity and margin over 0.4 s).
The whole card listens for clicks, but only the header is a button. That is deliberate: a button wrapping the paragraph and the figures would read all that text as its name. From the keyboard, Tab reaches the header, Enter or Space fires a click that bubbles up to the card, and aria-expanded switches to true or false. When folded, the details are also made invisible (visibility: hidden, applied 0.6 s after closing starts so the animation is not cut): a screen reader does not read text hidden on screen.
The three figures are painted in a violet-cyan gradient through background-clip: text (with its -webkit- form) and -webkit-text-fill-color: transparent. The height runs through max-height, because an auto height does not animate everywhere: 200 px leave room for a paragraph of three or four lines and the row of figures; longer content would be cut off, so raise the value. The ten-line script walks through every .fx-0406-root .card-morph: several cards live side by side, each with its own state.
Accessibility
- Keyboard: measured in Chromium 151 and Firefox 153, Tab reaches the header, Enter or Space opens then closes the card, and
aria-expandedfollows. The focus ring (2 px violet, 4 px offset) stays inside the card's padding. In Safari, Tab only stops on buttons if the keyboard setting asks for it (or with Option+Tab). - Screen readers: the header is announced as "Alice Laurent UI/UX Designer, button, collapsed"; the initials dot is hidden (
aria-hidden). When folded, the paragraph and figures are not read. - Reduced motion: under
prefers-reduced-motion: reduce, the card takes its 300 px at once, without bounce, and the details fade in over 0.3 s. Measured: no frame changes after 300 ms. - Contrast: the paragraph is white at 60% on a dark background (about 7:1); the figure captions, white at 40%, drop to about 4:1. Keep them short, or raise their opacity if your background is lighter.
Browser compatibility
CSS (transitions on width, max-height, opacity; background-clip: text) + 0.5 KB of JavaScript, no dependencies. Measured frame by frame in Chromium 151, Firefox 153 and WebKit 26: same bouncy widening, same unfolding details, same stop under reduced motion. The card's gradient background does not animate: it switches at once, under the width that does glide.
Without JavaScript, the card stays folded and its details cannot be reached: if the paragraph and figures matter, link to a full page. Without background-clip: text, the figures show as solid gradients (colored rectangles): use a plain color instead.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0406-root">
<div class="card-morph-wrapper">
<div class="card-morph">
<button type="button" class="card-header" aria-expanded="false">
<span class="card-avatar" aria-hidden="true">AL</span>
<span class="card-id"><span class="card-name">…</span><span class="card-role">…</span></span>
</button>
<div class="card-details">
<p>…</p>
<div class="card-stats">
<div class="card-stat"><div class="card-stat-value">142</div><div class="card-stat-label">…</div></div> … (3 figures)
</div>
</div>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Widths (CSS — width of .card-morph and .expanded) |
200 px folded, 300 px unfolded | The gap makes the effect: 220 → 340 px for longer text. On mobile, width: min(300px, 100%) keeps the unfolded card inside the screen. |
Maximum height of the details (CSS — max-height of .expanded .card-details) |
200 px | Raise it if your paragraph runs past four lines (300 px for six lines). Too large a value delays closing: the transition starts from that value, not from the real height. |
Bounce (CSS — transition: .6s cubic-bezier(.68, -.55, .27, 1.55)) |
0.6 s, back-up then overshoot | .4s ease gives an opening without bounce. Keep the same duration as max-height so width and height land together. |
Tones (CSS — background of the card, of .card-avatar and of .card-stat-value) |
violet #a855f7, cyan #06b6d4, pink #ec4899 |
Match all three to your brand. The unfolded card gets a slightly denser background and a 60 px shadow: keep that difference, it says the card is open. |
Content (HTML — .card-details) |
a paragraph and three figures | Put anything there (list, link, button). A link or button inside the details passes its click on to the card: add e.stopPropagation() to its click so it does not fold the card. |
FAQ
aria-expanded gives the state, and the mouse can still click anywhere on the card, since the card is what listens for the click.height: 0 to height: auto does not animate in every browser: it jumps. max-height from 0 to 200 px animates everywhere, with one limit: content taller than 200 px is cut off, and closing starts by "using up" the gap between 200 px and the real height. Chrome 129 can animate to auto with interpolate-size: allow-keywords; until Firefox and Safari follow, max-height is the choice that works everywhere.