Card Morph
The card scales up on click via a back-ease-out curve, fades out its preview and reveals full content with a scale-in fade — closes on an outside click.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cards has 41 effects, including 6 free. Explore the category →
3 usage examples



How it works
A click handler on .card-morph-expand calls classList.toggle('expanded'). A second listener on document detects outside clicks and removes the expanded class automatically — no close button needed.
The expansion is driven by a CSS transition: transition: .5s cubic-bezier(.175, .885, .32, 1.275). This back ease-out curve produces a slight overshoot then an elastic settle at the final scale(1.1) — no animation JavaScript at all. A purple drop shadow appears simultaneously via box-shadow.
The content swap relies on two layers stacked with position: absolute; inset: 0. The preview layer (.card-preview) fades to opacity: 0 in 0.3 s. The content layer (.card-full-content) starts at opacity: 0; transform: scale(.9) and transitions to opacity: 1; transform: scale(1) in 0.4 s, creating a centred scale-in reveal.
No requestAnimationFrame is used: the effect is 100% CSS transitions triggered by a class toggle. .card-morph-expand carries overflow: hidden — content never bleeds outside the rounded corners, even at scale(1.1).
Accessibility
- prefers-reduced-motion absent: the code has no
prefers-reduced-motionmedia query — scale and opacity transitions always run, even when the OS requests reduced motion. Add it manually if needed. - The clickable element is a
<div>with norole="button"orkeydownhandler — keyboard-inaccessible (Tab does not reach it, Enter/Space have no effect). - No
aria-expandedattribute: screen readers are not notified of the open/closed state. - Secondary text (
--text-muted: #8b8b93) on#12121abackground achieves a contrast ratio of approximately 4.7:1 — WCAG AA compliant (≥ 4.5:1). - Click target (280 × 180 px by default) far exceeds the 44 × 44 px WCAG 2.5.5 minimum.
Browser compatibility
Requires CSS transitions and classList — supported in all modern browsers since 2017. Zero external dependencies.
Without JS, the card stays in its initial state (preview visible). CSS styles are self-contained — no flash, no broken layout.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<div class="card-demo card-morph-expand" id="morphExpandCard">
<div class="card-preview">
<div class="card-preview-icon">
<!-- 24×24 SVG icon -->
</div>
<span>Click</span>
</div>
<div class="card-full-content">
<h5>Revealed Content Title</h5>
<p>Detailed description shown on expand.</p>
</div>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| --bg-card | #12121a | Card background color — replace with any color from your design system. |
| --radius-xl | 1rem | Corner radius — 0.5rem for a card look, 1.5rem for a pill look. |
| --border | rgba(255,255,255,0.08) | Card border color at rest — increase opacity for a more visible outline on light backgrounds. |
| --text-primary | #ffffff | Title color in the revealed content. |
| --text-muted | #8b8b93 | Secondary text color in the revealed content (WCAG AA ratio ≈ 4.7:1 on default --bg-card). |
| transition: .5s cubic-bezier(.175,.885,.32,1.275) | .5s back-ease-out | Duration and curve of the expansion — .3s ease-out for a snappier feel, .7s for a more theatrical gesture. |
| transform: scale(1.1) | scale(1.1) | Expansion amount in .card-morph-expand.expanded — scale(1.05) for subtle, scale(1.2) for dramatic. |
| box-shadow | rgba(99,102,241,.3) 0 25px 50px | Shadow color and spread on expand — change the RGBA color to match your brand accent. |
| linear-gradient(135deg,#6366f1,#8b5cf6) | indigo→violet | Gradient of the round icon in .card-preview-icon — replace with brand colors or a background image. |
FAQ
morphExpandCard — one instance only. For multiple cards, replace the getElementById with a loop: document.querySelectorAll('.card-morph-expand').forEach(card => { card.addEventListener('click', () => card.classList.toggle('expanded')); }); and add document.addEventListener('click', e => document.querySelectorAll('.card-morph-expand').forEach(c => { if (!c.contains(e.target)) c.classList.remove('expanded'); })).<div> is not focusable natively. Two fixes: replace it with a <button type="button" class="card-morph-expand"> (natively focusable, Enter/Space trigger click), or add tabindex="0" plus a keydown listener that calls classList.toggle('expanded') on Enter and Space. Also update aria-expanded via JS..card-morph-expand define transition: transform .35s ease-in, box-shadow .35s ease-in (fast close), then on .card-morph-expand.expanded override with transition: transform .5s cubic-bezier(.175,.885,.32,1.275), box-shadow .5s ease-out (elastic open). CSS applies the active rule at transition time.