Cards✨ Premium

Grid to List Toggle

Smooth grid ↔ list toggle using the FLIP technique — items physically reposition themselves while labels reveal on the fly.

CSSGridFLIP

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

App sidebar — Icon grid or condensed labelled menu — Grid to List Toggle example 1

① App sidebar — Icon grid or condensed labelled menu

WhenA dashboard offers two display modes: an icon grid for fast pointer navigation, and a compact labelled list for keyboard users or narrow screens.
WhyFLIP hides CSS Grid's rigidity by making each item feel like it physically slides to its new position — the interaction reads as natural rather than teleported.
Settings3 columns by default (repeat(3, 1fr)), transition at 0.35s for a snappier response, gap: 12px to open the grid, icon at 32px in grid and 24px in list (default values).
Product catalogue — Gallery view ↔ comparison list — Grid to List Toggle example 2

② Product catalogue — Gallery view ↔ comparison list

WhenA product page offers two modes: a gallery for visual browsing (large thumbnails), and a list for quick comparison (name + price side by side).
WhyThe label reveal via max-width: 0 → 200px with opacity makes the text appear to unfurl from the thumbnail — more elegant than a plain display: none.
Settingsrepeat(4, 1fr) for 4 columns on desktop, icon enlarged to 40 × 40 px in grid, --primary: #10b981 for a brand green, max-width: 280px for longer product labels.
Knowledge base — Topic cards or compact index — Grid to List Toggle example 3

③ Knowledge base — Topic cards or compact index

WhenA help centre or internal documentation lets users switch between a cards view (visual navigation) and a dense list (quick title scanning).
WhyFLIP preserves the user's spatial orientation — each topic visibly slides to its new position rather than the list rebuilding from scratch, reducing disorientation on mode change.
Settingsrepeat(2, 1fr) for 2 wide columns in grid mode, transition at 0.5s (default) for an ample animation, border-radius: 12px on items, padding: 16px for airy cards.

How it works

The container uses CSS Grid: in grid mode, grid-template-columns: repeat(3, 1fr) arranges 6 items in 2 rows. A click adds the .list-mode class, switching to grid-template-columns: 1fr — one full-width column. The issue: grid-template-columns is not an interpolable property in current browsers, so CSS cannot animate this change. JS takes over with the FLIP technique.

The FLIP (First-Last-Invert-Play) technique operates in four phases on each .grid-list-item: First — read current coordinates with getBoundingClientRect(); Last — apply the .list-mode class (Grid layout recalculates immediately); Invert — compute the δx/δy delta between old and new positions, then force transform: translate(δx, δy) without transition to snap the item back to its original visual position; Play — inside a requestAnimationFrame, re-enable the CSS transition and remove the transform, letting the item slide to its real destination.

Simultaneously, two CSS micro-transitions run on each item: the label (.item-text) goes from opacity: 0; max-width: 0 in grid mode to opacity: 1; max-width: 200px in list mode, unfurling from the icon; the icon (.item-icon) shrinks from 32 × 32 px to 24 × 24 px. Both transitions share the same cubic-bezier(.4, 0, .2, 1) as the FLIP, visually synchronising the text reveal with the item's movement.

The function setGridList(button, mode) is declared as a global (outside any IIFE) so it can be called from onclick HTML attributes. It finds the toggle group via t.parentElement.querySelectorAll('.morph-toggle') to update the .active state — making it possible to wire multiple instances on the same page by pointing each to a distinct target container.

Accessibility

  • prefers-reduced-motion absent: neither the CSS nor the JS checks this OS preference — the FLIP animation and max-width/opacity transitions run even if the user has enabled Reduce Motion. Must be added before accessible production use.
  • Toggle buttons are real <button> HTML elements — keyboard-activatable (Tab, Enter, Space) without extra JS.
  • No aria-pressed on the toggle buttons: the active state (class .active) is not exposed to screen readers.
  • The .grid-list-container has no role or aria-label — the region's semantics are not declared.
  • The #a5b4fc colour of labels visible in list mode against the dark background (~rgba(99,102,241,.15) on #0a0a0f) achieves a contrast ratio ≥ 5:1 — WCAG AA met for normal text.

Browser compatibility

Requires CSS Grid Level 1 and requestAnimationFrame — supported in all modern browsers. grid-template-columns is not natively interpolable: smoothness depends entirely on the JS FLIP technique.

Chrome 110+✓ Full
Firefox 110+✓ Full
Safari 16+✓ Full
Edge 110+✓ Full
Mobile iOS✓ Touch OK
Android Chrome✓ Full

If JavaScript is disabled, the toggle buttons have no effect and the view stays locked in grid mode by default — content remains readable and the layout does not break.

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="setGridList(this, 'grid')">Grid</button>
    <button class="morph-toggle" onclick="setGridList(this, 'list')">List</button>
  </div>
  <div class="grid-list-container" id="gridListDemo">
    <div class="grid-list-item">
      <div class="item-icon"></div>
      <span class="item-text">Label</span>
    </div>
    <!-- repeat .grid-list-item as many times as needed -->
  </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 CSS variable controlling toggle button colour, item borders and background gradient. Swap for your brand colour.
grid-template-columns repeat(3, 1fr) Columns in grid mode. Change 3 to 2 (wide items), 4 (compact items) or 6 (icon-only, no text).
transition: .5s 0.5s cubic-bezier(.4, 0, .2, 1) Duration and easing of the FLIP and max-width/opacity transitions. 0.3s for a snappy feel, 0.8s for a cinematic effect.
.item-icon width/height 32px / 32px (grid), 24px / 24px (list) Icon size per mode. Go to 40px in grid for more prominent icons, or 20px in list for an ultra-compact view.
.item-text max-width 200px (list mode) Max width of the label revealed in list mode. From 120px for short tags to 320px for long titles.
gap 8px Spacing between items (both modes). 12–16px for an airy layout, 4px for an ultra-dense list.
.grid-list-item padding 12px Internal padding of each item. 16–20px for airy items with large icons, 8px for a compact list.
.grid-list-item border-radius 8px 4px for a strict rectangular style, 12px for soft cards, 50% (with square items and icon only) for circular badges.

FAQ

grid-template-columns is not an interpolable property — browsers cannot compute intermediate states between repeat(3, 1fr) and 1fr. The FLIP technique works around this: it measures positions before and after the layout change, instantly snaps each item back with transform: translate(δx, δy), then removes the transform inside a requestAnimationFrame — the CSS transition takes over and creates the illusion of a physical slide.
The setGridList function hard-codes document.getElementById('gridListDemo'). For multiple instances, duplicate the HTML structure with a distinct id per container, then create a version of the function specific to each id — or adapt it to traverse the DOM from the button: t.closest('.your-wrapper').querySelector('.grid-list-container') before running the rest of the FLIP logic.
Yes — native <button> HTML elements respond to touch events without extra configuration. There is no built-in swipe gesture; if you want to trigger the toggle on a swipe, add a touchstart or pointerdown listener to the container and call setGridList manually.