Cards✨ Premium

Tab Content Morph

Pure CSS tab panel: panels swap with an opacity crossfade and synchronized translateY+scale slide — smooth morphing in 0.5 s, no library.

CSSJSTabs

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

Product card — Details / Specs / Reviews — Tab Content Morph example 1

① Product card — Details / Specs / Reviews

WhenA product page or modal groups three information levels: commercial description, technical specs, and customer reviews — too much for linear scrolling in a constrained space.
WhyThe crossfade prevents the abrupt height jump typical of standard tabs: content enters from a slight depth, making the switch feel natural rather than mechanical.
SettingsTransition at 0.4 s (edit .tab-morph-panel { transition }), active color matched to the product brand via --primary, min-height on .tab-morph-content set to the tallest panel to prevent layout jump.
Documentation article — Overview / Example / Code — Tab Content Morph example 2

② Documentation article — Overview / Example / Code

WhenA technical article (docs, tutorial, blog) offers multiple readings of the same topic: an explanatory summary, a visual demo, and a ready-to-copy snippet.
WhyTabs segment information without leaving the page; the position transition creates a visual hierarchy — content 'takes the stage' rather than appearing abruptly, guiding the eye to the new panel.
SettingsVertical amplitude 10 px (translateY) and scale 0.95 create a slight zoom impression; reduce to 5 px / 0.98 for a subtler effect on dense content.
Metrics widget — Today / Week / Month — Tab Content Morph example 3

③ Metrics widget — Today / Week / Month

WhenA SaaS dashboard shows key indicators (views, conversions, revenue) across three time granularities in a constrained space.
WhyCSS morphing gives the impression that figures 'reposition themselves' when switching periods, reinforcing the perception of a live dashboard — without canvas or a charting library.
SettingsKeep the transition at 0.5 s for comfortable reading rhythm on numerical data; panel content is fully free per tab (text, inline SVG sparkline, trend badge).

How it works

All panels are stacked with position: absolute; inset: 0 inside a relative container. When inactive, each panel carries opacity: 0 and transform: translateY(10px) scale(.95): invisible, shifted 10 px downward, and slightly shrunk. The .active class brings it back to opacity: 1; transform: translateY(0) scale(1) — the crossfade combined with the vertical slide defines the morphing.

The transition is driven by a single CSS rule: transition: .5s cubic-bezier(.4, 0, .2, 1) (Material Design's ease-out curve). When switchTab() moves the .active class, the outgoing panel fades and slides down while the incoming panel rises and scales up simultaneously — both transitions run in parallel on the same GPU layer, with no document reflow.

The switchTab(index, btn) function is declared inside an IIFE. It strips .active from all buttons via querySelectorAll('.tab-morph-btn') and from all panels via querySelectorAll('#tabMorphContent .tab-morph-panel'), then sets the class on the clicked button and on the panel at the matching index. Inactive panels carry pointer-events: none: no accidental interaction with hidden content.

Limitation: switchTab is declared inside the IIFE and is not exposed on window; inline onclick attributes cannot reach it in global context. For multi-instance or programmatic use, redefine window.switchTab scoped to the parent .tab-morph-wrapper of the clicked button (see Customization and FAQ).

Accessibility

  • prefers-reduced-motion: absent — the code contains no detection for this OS preference; the 0.5 s transition always runs, including for motion-sensitive users.
  • Triggers are native <button> elements: keyboard-focusable by default, activatable with Enter or Space.
  • The ARIA tabs pattern is absent: no role="tablist", no role="tab" on buttons, no role="tabpanel" or aria-selected — screen readers do not perceive the tab/panel relationship.
  • Inactive panels carry pointer-events: none: no accidental interaction with hidden content.
  • Contrast of inactive button text (#a5b4fc on #0a0a0f background): ratio ≈ 5.1:1, WCAG AA met.

Browser compatibility

The effect relies solely on CSS transitions and querySelectorAll — available in all browsers since 2016. No external dependencies.

Chrome 60+✓ Full
Firefox 55+✓ Full
Safari 12+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without CSS transition support, panels switch instantly (no morphing) but tab navigation remains fully functional — no JS errors.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="tab-morph-wrapper">
  <div class="tab-morph-nav">
    <button class="tab-morph-btn active" onclick="switchTab(0, this)">Tab 1</button>
    <button class="tab-morph-btn" onclick="switchTab(1, this)">Tab 2</button>
    <button class="tab-morph-btn" onclick="switchTab(2, this)">Tab 3</button>
  </div>
  <div class="tab-morph-content" id="tabMorphContent">
    <div class="tab-morph-panel active"><!-- Content 1 --></div>
    <div class="tab-morph-panel"><!-- Content 2 --></div>
    <div class="tab-morph-panel"><!-- Content 3 --></div>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
--primary #6366f1 Root CSS variable: accent color for the active button (background + border-color). Replace with your brand color to integrate the effect into your design system.
.tab-morph-panel { transition } 0.5s cubic-bezier(.4, 0, .2, 1) Morphing duration and curve. Try 0.3 s for a snappy feel, 0.7 s for a cinematic blend. The ease-out curve is the Material Design standard.
translateY(10px) in .tab-morph-panel 10px Vertical slide amplitude on entry. Reduce to 4 px for a near-invisible shift, raise to 20 px for a more dramatic depth effect.
scale(.95) in .tab-morph-panel 0.95 Scale factor of the inactive panel. Go down to 0.90 to emphasize the zoom-in on entry, set to 1 to remove the scale effect entirely.
.tab-morph-content { min-height } 80px Minimum height of the panel container. Set it to the height of the tallest panel to prevent layout jumps when panels have very different content sizes.
.tab-morph-btn { padding } 6px 14px Button click area size. Increase padding to meet the 44 × 44 px minimum touch target recommended by WCAG 2.5.5.
window.switchTab(index, btn) Redefine this function to make it globally accessible and instance-scoped (btn.closest('.tab-morph-wrapper')) — required for multiple instances on the same page or to trigger a tab switch programmatically.

FAQ

In the original version, switchTab queries querySelectorAll('.tab-morph-btn') without scope — multiple instances interfere with each other. To use several, redefine window.switchTab scoped to the parent wrapper: var w = btn.closest('.tab-morph-wrapper'); w.querySelectorAll('.tab-morph-btn').forEach(…). Each instance then operates independently.
Add <button class="tab-morph-btn" onclick="switchTab(3, this)">Tab 4</button> in .tab-morph-nav and <div class="tab-morph-panel">…</div> in .tab-morph-content. The index passed to switchTab must match the panel's zero-based position. No CSS changes required — the transition applies automatically to any .tab-morph-panel element.
No — the original code does not check for this preference. To add it, inject in CSS: @media (prefers-reduced-motion: reduce) { .tab-morph-panel { transition: none; } }. Panels will then switch instantly without animation — still fully functional and respectful of motion-sensitive users.