Cards✨ Premium

Split Screen Morph

Two flex panels morph between 50/50, 70/30, and single panel — a button adds a mode class, the CSS cubic-bezier transition handles the animation without a single line of animation JS.

CSSFlexLayout

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

🎈

① Online editor — Code and preview side by side

WhenIn an online IDE, interactive sandbox, or technical tutorial: the user widens the code area or switches to a full-frame preview depending on the current step.
WhyThe 50/50 mode balances input and output; 70/30 frees space for long lines without hiding the preview; single-panel mode goes full-screen preview without a page change.
Settingstransition: .35s cubic-bezier(.4,0,.2,1) on .split-panel (snappier for a tool), panel-a background #0d1117 (code), panel-b #161b22 (preview), button labels: 'Code', '70/30', 'Preview'.
🎊

② Technical documentation — Content and API reference sidebar

WhenA documentation page shows the main article on the left and an API reference (parameters, signatures) on the right — the user closes the sidebar to focus on a dense section.
WhyThe 70/30 mode keeps the reference visible without over-squeezing the article; single-panel mode opens the text to full width for deep reading.
Settingstransition: .6s cubic-bezier(.4,0,.2,1) (default), panel-a background #12121c (article), panel-b #0e0e1a slightly distinct (reference), button labels: '50/50', '70/30', 'Full screen'.

③ Pricing comparison — Starter vs Pro side by side

WhenA landing page shows two plans side by side; the user switches to isolate the plan they want or returns to the comparison view before choosing.
WhyThe smooth 50/50 → single transition directs attention without reloading; initializing in mode-70-30 on the Pro side visually highlights the recommended plan on first load.
Settingstransition: .8s cubic-bezier(.4,0,.2,1) (wider transition for a hero section), initialize the wrapper in mode-70-30 on the Pro side, contrasting color palettes for the two panels.

How it works

The .split-screen-wrapper container is a flex row with overflow: hidden. Each panel starts at flex: 1 1 0% (50/50 by default). The .mode-70-30 class overrides the ratios to flex: 7 1 0% and flex: 3 1 0% (7 + 3 = 10, i.e. 70% and 30%). The .mode-single class collapses panel B to flex: 0 1 0% with min-width: 0; overflow: hidden; font-size: 0 — the browser shrinks it to zero width, content disappears cleanly.

All the smoothness comes from a single CSS rule: transition: .6s cubic-bezier(.4, 0, .2, 1) on .split-panel. The browser interpolates the flex-grow value between states (CSS Transitions Level 1, interpolable property). The cubic-bezier curve (Material Design's standard easing) produces an initial acceleration then a marked slow-down — the motion feels natural. No JS loop, no requestAnimationFrame — zero CPU outside the transition.

setSplitMode(btn, mode) does three things: it removes .active from all .morph-toggle buttons in the same parent (btn.parentElement.querySelectorAll), adds it to the clicked button, then strips both mode classes from the wrapper before applying a new one. The call to document.getElementById('splitDemo') is hardcoded — one instance per page as-is (see FAQ for multi-instance).

Accessibility

  • prefers-reduced-motion absent: the flex transition animates regardless of system settings. For production, add @media (prefers-reduced-motion: reduce) { .split-panel { transition: none } }.
  • Buttons are native <button> elements with inline onclick — keyboard-navigable (Tab + Enter or Space) without extra work.
  • No aria-pressed on the toggles: active state is visual only (the .active class). Add aria-pressed="true/false" inside setSplitMode for screen readers.
  • No role or aria-label on .split-screen-wrapper — panels should carry their own landmarks or labels when content is meaningful.
  • Contrast of 'Panel A / Panel B' labels — white text on purple gradient (#6366f1 → #818cf8): ratio ≥ 4.5:1 (WCAG AA met).

Browser compatibility

Requires CSS Flexbox and CSS Transitions — natively supported in all modern browsers since 2016. Zero external dependencies.

Chrome 88+✓ Full
Firefox 87+✓ Full
Safari 15+✓ Full
Edge 88+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without CSS Transitions (IE 9 and below), panels switch width instantly — no JS errors, mode switching stays fully functional.

The code

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

index.html — structure
<div class="demo-preview">
  <div class="morph-toggle-group">
    <button class="morph-toggle active" onclick="setSplitMode(this, '50-50')">50/50</button>
    <button class="morph-toggle" onclick="setSplitMode(this, '70-30')">70/30</button>
    <button class="morph-toggle" onclick="setSplitMode(this, 'single')">Single</button>
  </div>
  <div class="split-screen-wrapper" id="splitDemo">
    <div class="split-panel split-panel-a">Panel A</div>
    <div class="split-panel split-panel-b">Panel B</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
transition on .split-panel .6s cubic-bezier(.4, 0, .2, 1) Animation duration and curve. Example: .35s ease for a responsive tool, .9s cubic-bezier(.34,1.56,.64,1) for an elastic bounce.
--primary (CSS var) #6366f1 Toggle button color (border, active background, active text). Swap for your project's primary color.
flex on .mode-70-30 .split-panel-a 7 Share of the large panel in asymmetric mode. 6 = 60/40, 8 = 80/20 — update .split-panel-b to match.
flex on .mode-70-30 .split-panel-b 3 Share of the small panel. Must total the same sum as .split-panel-a (e.g. 8+2, 7+3, 6+4).
gap on .split-screen-wrapper 4px Gutter between the two panels. 0 for a flush joint, 12px for an airy gap.
border-radius on .split-screen-wrapper 8px Container corner radius. 0 for full bleed, 16px for a card or modal.
setSplitMode(btn, mode) External call from any code: trigger a mode change from a scroll event, a timer, or a keyboard shortcut without touching the built-in buttons.

FAQ

Add two CSS rules: .split-screen-wrapper.mode-30-70 .split-panel-a { flex: 3 1 0% } and .mode-30-70 .split-panel-b { flex: 7 1 0% }. Add a button with onclick="setSplitMode(this, '30-70')", then extend the condition inside setSplitMode: '30-70' === t ? o.classList.add('mode-30-70') : ….
Yes. Replace <div class="split-panel-a">Panel A</div> with any HTML. Add min-width: 0 on the panels if the content is rigid (tables, code editors) to allow shrinking below its natural size — the property is already present on .mode-single .split-panel-b as a reference.
setSplitMode targets getElementById('splitDemo') by hardcoded id. For multiple instances, rename each wrapper (splitDemo2, etc.) and create a variant setSplitModeById(btn, mode, id) that takes the id as a parameter, or read it from a data-target attribute on the button (btn.dataset.target).