{"schemaVersion":"1.0","type":"Article","slug":"selection-through-the-looking-glass-a-3d-stack-component-for-mantine-qhlf6","url":"https://api.zyvop.com/selection-through-the-looking-glass-a-3d-stack-component-for-mantine-qhlf6","title":"Selection Through the Looking Glass: A 3D Stack Component for Mantine","subtitle":null,"tldr":"A Mantine component that turns flat item selection into a spatial, 3D card-stack browsing experience...","keywords":["webdev","TypeScript","webcomponents","react"],"entities":["Giovambattista Fazioli","Senior Full-stack Engineer, Lead Developer","webdev","TypeScript","webcomponents","react","ZyVOP"],"keyTakeaways":["A Mantine component that turns flat item selection into a spatial, 3D card-stack browsing experience — with keyboard, wheel, touch, and click navigation built in.","Introduction Remember the first time you used macOS Time Machine?.","That moment when your desktop peeled away into an infinite corridor of snapshots, and navigating through time suddenly felt physical."],"headings":["Introduction","What is DepthSelect?","✨ Key Features","3D Perspective Stack","Multi-Input Navigation","Built-in Controls with controlsProps","Compound Component Pattern","Loop Mode","🚀 Getting Started","Props &amp; API","DepthSelect","DepthSelect.Controls","🎨 Styles API","⚡ Performance","Live Demo &amp; Documentation","Links"],"outboundLinks":["https://gfazioli.github.io/mantine-depth-select/","https://www.npmjs.com/package/@gfazioli/mantine-depth-select","https://github.com/gfazioli/mantine-depth-select","https://mantine-extensions.vercel.app/"],"contentText":"A Mantine component that turns flat item selection into a spatial, 3D card-stack browsing experience — with keyboard, wheel, touch, and click navigation built in. Introduction Remember the first time you used macOS Time Machine? That moment when your desktop peeled away into an infinite corridor of snapshots, and navigating through time suddenly felt physical. That spatial metaphor stuck with us. mantine-depth-select brings that same sense of depth to any selection UI in your React application — pricing plans, document versions, onboarding steps, or anything else where browsing through stacked content beats scrolling a flat list. It's a single component with zero extra dependencies, built entirely on Mantine 8's Styles API. What is DepthSelect? DepthSelect renders an array of items as a 3D stack of cards. The frontmost card is the active selection; cards behind it recede with progressive scale, vertical offset, opacity, and blur — all configurable per-level. When the user navigates (via keyboard, mouse wheel, trackpad swipe, click, or arrow controls), cards animate smoothly in and out of the stack with CSS transitions. The component follows Mantine's compound component pattern. Built-in arrow controls appear by default, but you can hide them and wire up your own UI through controlled value/onChange. It supports both string and numeric values, loop mode, and respects prefers-reduced-motion. ✨ Key Features 3D Perspective Stack Each card in the stack receives progressive CSS transforms based on its depth. Four props control the effect: &lt;DepthSelectdata={items}scaleStep={0.1} // scale reduction per level (default)translateYStep={30} // vertical offset in px per levelopacityStep={0.15} // opacity reduction per levelblurStep={1} // blur in px per levelw={400}h={300}/&gt; Enter fullscreen mode Exit fullscreen mode Cards that exit the stack (when navigating forward) animate toward the viewer with a scale-up and fade-out. Cards entering from behind animate in from the deepest visible position. Multi-Input Navigation Every common input method works out of the box: Keyboard: ArrowUp/ArrowDown to step through, Home/End to jump Mouse wheel / Trackpad: Scroll over the component to navigate (page scroll is blocked automatically). Disable with withScrollNavigation={false} Touch: Vertical swipe gestures for mobile Click: Click the second card (depth 1) to advance Controls: Built-in arrow buttons with optional label Built-in Controls with controlsProps The default controls sit to the right of the stack. You can reposition them, add a label, set a fixed width, change alignment, or swap the icons — all through a single controlsProps object: &lt;DepthSelectdata={items}controlsPosition=\"left\"controlsProps={{ w: 100, justify: 'end', labelFormatter: (item) =&gt; `Step ${item.value}`, upIcon: &lt;IconChevronUp size={16} /&gt;, downIcon: &lt;IconChevronDown size={16} /&gt;,}}w={400}h={300}/&gt; Enter fullscreen mode Exit fullscreen mode Compound Component Pattern Need full layout control? Set withControls={false} and use DepthSelect.Controls as a child: &lt;DepthSelect data={items} withControls={false} w={400} h={300}&gt;&lt;DepthSelect.Controls labelFormatter={(item) =&gt; String(item.value)} /&gt;&lt;/DepthSelect&gt; Enter fullscreen mode Exit fullscreen mode Or skip the built-in controls entirely and build your own navigation with value and onChange. Loop Mode Enable loop to let navigation wrap from the last item back to the first (and vice versa). The arrow controls never show a disabled state in loop mode: &lt;DepthSelect data={items} loop w={400} h={200} /&gt; Enter fullscreen mode Exit fullscreen mode 🚀 Getting Started npm install @gfazioli/mantine-depth-select Enter fullscreen mode Exit fullscreen mode Import the styles at the root of your application: import '@gfazioli/mantine-depth-select/styles.css'; Enter fullscreen mode Exit fullscreen mode Minimal example: import { Card, Text } from '@mantine/core';import { DepthSelect, DepthSelectItem } from '@gfazioli/mantine-depth-select'; const data: DepthSelectItem[] = [{ value: 'first', view: &lt;Card p=\"lg\" withBorder h=\"100%\"&gt;&lt;Text&gt;First item&lt;/Text&gt;&lt;/Card&gt; },{ value: 'second', view: &lt;Card p=\"lg\" withBorder h=\"100%\"&gt;&lt;Text&gt;Second item&lt;/Text&gt;&lt;/Card&gt; },{ value: 'third', view: &lt;Card p=\"lg\" withBorder h=\"100%\"&gt;&lt;Text&gt;Third item&lt;/Text&gt;&lt;/Card&gt; },]; function Demo() {return &lt;DepthSelect data={data} w={400} h={200} /&gt;;} Enter fullscreen mode Exit fullscreen mode The w and h props define the area where cards live. Cards should use h=\"100%\" to fill the available height. Props &amp; API DepthSelect data — DepthSelectItem[] — Array of { value, view } objects. value can be string or number, view is any ReactNode value / defaultValue / onChange — Standard controlled/uncontrolled pattern w / h — Dimensions of the card area (passed to the inner stack container) visibleCards — Number of cards visible in the stack (default: 4) withControls — Show built-in arrow controls (default: true) controlsPosition — \"right\" (default) or \"left\" controlsProps — All DepthSelectControlsProps passed to the built-in controls loop — Enable wrap-around navigation (default: false) withScrollNavigation — Enable mouse wheel / trackpad navigation (default: true) transitionDuration — Animation duration in ms (default: 400) scaleStep / translateYStep / opacityStep / blurStep — Per-level depth effect parameters DepthSelect.Controls labelFormatter — (item: DepthSelectItem) =&gt; ReactNode — Custom label between arrows upIcon / downIcon — Custom icons for the arrow buttons justify — Vertical alignment: \"start\", \"center\" (default), \"end\" Plus all Mantine BoxProps (w, h, style, etc.) 🎨 Styles API Selectors: root, stack, card, controls, controlUp, controlDown, controlLabel Data attributes: data-active — Frontmost (selected) card data-depth=\"N\" — Card depth level (0 = front) data-exited — Card that has animated out data-disabled — Disabled control button data-controls-position — \"right\" or \"left\" on root CSS variables: --ds-transition-duration, --ds-scale-step, --ds-translate-y-step, --ds-opacity-step, --ds-blur-step, --ds-visible-cards ⚡ Performance Render window: Only activeIndex - 1 through activeIndex + visibleCards + 1 are in the DOM. A dataset of 100 items renders ~6 nodes Memoized styles: Card CSS is computed once per navigation change via useMemo Targeted will-change: Only visible cards get GPU layer promotion; hidden cards use will-change: auto Non-passive wheel listener: Blocks page scroll without layout thrashing prefers-reduced-motion: Transitions are disabled automatically for users who prefer reduced motion Live Demo &amp; Documentation Explore interactive demos — including a configurator with live props, rich card examples, pricing plan selector, version history browser, onboarding wizard, and more: gfazioli.github.io/mantine-depth-select Links 📦 npm: @gfazioli/mantine-depth-select 📖 Docs: gfazioli.github.io/mantine-depth-select 🔗 GitHub: github.com/gfazioli/mantine-depth-select 🧩 Mantine Extensions: mantine-extensions.vercel.app","contentHash":"sha256:7091fc3a158bce321cfde2d4d5655cd4b5f5d7a6a8c89c33ca3e47bb04b2c102","authorName":"Giovambattista Fazioli","authorUrl":"https://api.zyvop.com/author/giovambattista","authorSameAs":["https://gfazioli.github.io","https://github.com/gfazioli","https://x.com/gfazioli","https://www.linkedin.com/in/giovambattistafazioli/"],"category":null,"tags":["webdev","TypeScript","webcomponents","react"],"audience":"Readers researching webdev","tone":"Professional, senior full-stack engineer, lead developer perspective","readingTimeMinutes":5,"wordCount":1001,"faqs":null,"primaryTopic":"webdev","publishedAt":"2026-09-04T09:25:49.151Z","updatedAt":"2026-09-04T09:25:49.151Z","canonicalUrl":"https://dev.to/undolog/selection-through-the-looking-glass-a-3d-stack-component-for-mantine-4mdh"}