Cards✨ Premium

Bento Card Stack

Bento grid with depth-offset card stacks — on hover, each stack fans out via a CSS spring transition, no JavaScript required.

CSSStackCards

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

Template library — Design resource picker — Bento Card Stack example 1

① Template library — Design resource picker

WhenInside a no-code tool or visual editor where templates are organized by collection (UI Kits, illustrations, icons) in a side panel or selection modal.
WhyThe stack reveals that multiple resources exist per collection; the hover fan exposes them all without a page change — the user instantly understands the structure without needing a dropdown.
SettingsCategory-linked color palette (blue gradient for UI, green for icons, orange for illustrations), span 2×2 cells for main collections, mini-cards for format variants. Keep transition 0.4s cubic-bezier(.175,.885,.32,1.275).
SaaS landing — Feature showcase in a bento grid — Bento Card Stack example 2

② SaaS landing — Feature showcase in a bento grid

WhenIn the hero or features section of a landing page, to display several feature groups compactly and interactively below the main headline.
WhyBento grids are a current landing-page standard; interactive stacks add depth and invite exploration without aggressive auto-animation — users discover details at their own pace.
SettingsGradients matched to the product's visual identity, span 2×2 cells for key features (with UI screenshots), mini-cards for integrations. Background --bg-elevated: #1a1a24.
Creative portfolio — Project gallery by category — Bento Card Stack example 3

③ Creative portfolio — Project gallery by category

WhenIn a designer or developer portfolio, to group projects by type (branding, web, motion) and reveal them on hover without extra navigation.
WhyStacked cards naturally evoke a project deck; the fan-out invites exploration per category and makes navigation memorable — no complex JS or state management needed.
SettingsDark background --bg-elevated: #1a1a24, per-category gradient, project name in the .stacked-card span. Mini-cards for derivative media (print, motion clips).

How it works

The structure relies on a 4×3 CSS Grid (repeat(4,1fr) columns, repeat(3,1fr) rows). Two large cells span 2/span 2 for the main stacks; two bottom cells span 2 columns for the mini-cards — all inside .bento-container with max-width: 600px and aspect-ratio: 4/3.

The stacking effect is pure CSS: three .stacked-card are position: absolute, centered via translate(-50%, -50%). The second card is offset by translateY(8px) scale(0.95) and the third by translateY(16px) scale(0.90) — no CSS 3D or perspective, depth is simulated solely through Y offset and scale reduction.

On :hover over a bento cell, three simultaneous transitions 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) (spring with slight overshoot) play: the front card moves to translateX(-30%) rotate(-10deg), the middle card rises 5 px, the back card moves to translateX(30%) rotate(10deg) — forming a symmetrical fan. The .mini-card elements in the bottom cells follow the same logic with translateY and a light rotation.

The JS exposes replayAnimation() to replay the .bento-animated variant (entry via @keyframes bentoFadeIn) — it forces a reflow via offsetHeight then resets the CSS animation. The card-stack mechanism itself is 100% CSS with no JS initialization needed.

Accessibility

  • prefers-reduced-motion not implemented: the code contains no prefers-reduced-motion media query. CSS transitions run even when the user has requested reduced motion. Minimal fix: @media (prefers-reduced-motion: reduce) { .stacked-card, .mini-card { transition: none; } }.
  • The animation is triggered by CSS :hover only — no keyboard equivalent (:focus-within absent). Keyboard users do not see the fan-out.
  • No ARIA semantics on the cells: .bento-cell are non-interactive <div> elements with no role or aria-label. Add role="group" and aria-label on each stack if the card content is meaningful.
  • The card color gradients (close indigo-violet shades) provide limited contrast for white text overlaid on top — verify with your custom palette using a WCAG contrast tool.
  • .mini-card elements are purely decorative <div> with no text content: no text alternative is required as long as they remain visual.

Browser compatibility

Requires CSS Grid and CSS transitions — supported in all modern browsers since 2017. Zero external dependencies.

Chrome 80+✓ Full
Firefox 75+✓ Full
Safari 13+✓ Full
Edge 80+✓ Full
Mobile iOS✓ Hover absent — rest state
Android Chrome✓ Hover absent — rest state

On touch screens, CSS :hover does not fire: cards stay stacked in their rest state, still readable. No JS errors.

The code

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

index.html — structure
<div class="bento-container">
  <div class="bento-grid bento-cardstack">

    <!-- Wide cell 1: 3-card stack -->
    <div class="bento-cell cell-1">
      <div class="card-stack">
        <div class="stacked-card"><span>Card 3</span></div>
        <div class="stacked-card"><span>Card 2</span></div>
        <div class="stacked-card"><span>Card 1</span></div>
      </div>
    </div>

    <!-- Wide cell 2: 3-card stack -->
    <div class="bento-cell cell-2">
      <div class="card-stack">
        <div class="stacked-card"><span>C</span></div>
        <div class="stacked-card"><span>B</span></div>
        <div class="stacked-card"><span>A</span></div>
      </div>
    </div>

    <!-- Bottom-left cell: mini-cards -->
    <div class="bento-cell cell-3">
      <div class="mini-cards">
        <div class="mini-card"></div>
        <div class="mini-card"></div>
        <div class="mini-card"></div>
      </div>
    </div>

    <!-- Bottom-right cell: mini-cards -->
    <div class="bento-cell cell-4">
      <div class="mini-cards">
        <div class="mini-card"></div>
        <div class="mini-card"></div>
        <div class="mini-card"></div>
      </div>
    </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
--bg-elevated #1a1a24 Background of bento cells containing the stacks — change for stronger contrast or a brand tint.
--bg-card #12121a Base background of bento cells without the elevated variant.
--border rgba(255,255,255,0.08) Border of stacked cards. Raise opacity to 0.18 for more visible outlines.
--radius-md 0.5rem Corner radius of .stacked-card elements. Set to 0 for square cards or 1rem for more roundness.
.stacked-card:nth-child(n) background indigo→violet gradients Replace the linear-gradient on each :nth-child(1/2/3) to customize the card palette one by one.
translateX(±30%) in :hover ±30% Lateral spread on hover. ±45% for a wide fan, ±15% for a subtle spread.
rotate(±10deg) in :hover ±10deg Rotation angle of side cards on hover. 0deg = spread without rotation, 18deg = pronounced fan.
transition on .stacked-card 0.4s cubic-bezier(.175,.885,.32,1.275) Duration and easing of the spring animation. Use 0.25s ease-out for a subdued feel or 0.6s for a more elastic bounce.

FAQ

Add a 4th <div class="stacked-card"> as the first child (it will be the deepest visually). Then add its rest rule: .bento-cardstack .stacked-card:nth-child(4) { transform: translate(-50%,-50%) translateY(24px) scale(0.85); z-index: 0; } and the matching hover rule with a wider translateX.
No — CSS :hover does not fire on touch. To enable it on tap, add a touchstart listener that toggles an .is-open class on the cell, then target .bento-cell.is-open .stacked-card in your CSS fan-out rules instead of :hover.
Yes. Update the hover transforms: front card → translate(-50%,-50%) translateY(-35%) rotate(-8deg); middle card → translate(-50%,-65%); back card → translate(-50%,-50%) translateY(35%) rotate(8deg). The cubic-bezier and duration stay unchanged.