DEV Community

Cover image for Mantine Parallax v3 — From Tilt Card to Interactive Card System
Giovambattista Fazioli
Giovambattista Fazioli

Posted on

Mantine Parallax v3 — From Tilt Card to Interactive Card System

Spring physics, gyroscope, glare effects, compound layers, keyboard accessibility, and a 60fps performance rewrite — all in one major release.

Introduction

Version 3.0.0 of @gfazioli/mantine-parallax is the biggest update since the package was first published. What started as a simple Apple TV-style tilt card has evolved into a full-featured interactive card system with physically-based animations, mobile device support, and first-class accessibility. Under the hood, the rendering pipeline has been completely rewritten to deliver smooth 60fps updates with zero unnecessary React re-renders.

This release adds 13 new features, fixes 5 bugs, and includes 4 performance/architecture improvements — while staying fully compatible with Mantine 8.x and React 19.


What's New

Parallax.Layer — Compound Component for Multi-Depth Parallax

The new Parallax.Layer compound component replaces the legacy contentParallax + cloneElement approach. Each layer moves independently based on a depth multiplier, giving you precise control over parallax intensity per element.

<Parallax>
  <Parallax.Layer depth={1}>
    <Text>Background — subtle movement</Text>
  </Parallax.Layer>
  <Parallax.Layer depth={3}>
    <Title>Foreground — dramatic movement</Title>
  </Parallax.Layer>
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Layers automatically consume rotation state via React Context — no prop drilling, no cloneElement magic. The legacy contentParallax prop still works but Parallax.Layer is the recommended approach going forward.

Spring Physics Animation

Enable physically-based movement with overshoot and oscillation using a damped harmonic oscillator. The card feels alive — it bounces, settles, and responds organically to cursor movement.

<Parallax
  springEffect
  springStiffness={150}  // higher = snappier
  springDamping={12}     // higher = less bounce
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Spring parameters can be changed mid-animation — new values take effect immediately with no residual momentum from previous settings.

Glare Reflection Effect

A glare band follows the cursor across the card surface, simulating light reflecting off a glossy surface.

<Parallax
  glareEffect
  glareColor="rgba(255, 255, 255, 0.4)"
  glareMaxOpacity={0.4}
  glareSize={30}
  glareOverlay
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Dynamic Shadow Effect

A shadow that shifts opposite to the card rotation, creating the illusion of a light source from above. The shadow updates at 60fps during hover and smoothly transitions back to zero on leave.

<Parallax
  shadowEffect
  shadowColor="rgba(0, 0, 0, 0.4)"
  shadowBlur={30}
  shadowOffset={0.8}
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Gyroscope Support

On mobile devices, the card tilts based on physical device orientation via the DeviceOrientation API. On iOS 13+, permission is requested on the first user interaction — with built-in guards against duplicate permission prompts.

<Parallax
  gyroscopeEnabled
  gyroscopeSensitivity={1}
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Keyboard Accessibility

Arrow keys tilt the card, Escape resets it. When enabled, the component adds proper ARIA attributes (tabIndex, role="group", aria-roledescription, aria-label) for screen reader support.

<Parallax
  keyboardEnabled
  keyboardStep={5}  // degrees per key press
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Touch Support

Touch interactions are enabled by default. The card responds to finger movement on mobile, with touch-action: none on the outer container to prevent scroll interference.

<Parallax touchEnabled>{children}</Parallax>
Enter fullscreen mode Exit fullscreen mode

Mantine radius Prop

Standard Mantine radius prop for controlling border radius via the --parallax-radius CSS variable. Supports theme tokens, CSS strings, or pixel numbers.

<Parallax radius="md">...</Parallax>
<Parallax radius={16}>...</Parallax>
Enter fullscreen mode Exit fullscreen mode

Rotation Control Props

Fine-tune how the card responds to interaction:

<Parallax
  resetOnLeave={false}    // card holds its tilt after mouse leave
  invertRotation           // card tilts away from cursor
  maxRotation={15}         // clamp rotation to ±15 degrees
  hoverScale={1.05}        // subtle scale-up on hover
/>
Enter fullscreen mode Exit fullscreen mode

Transition Customization

Control the transition timing for enter/leave animations:

<Parallax
  transitionDuration={300}    // ms
  transitionEasing="ease-out" // any CSS easing function
/>
Enter fullscreen mode Exit fullscreen mode

onRotationChange Callback

Subscribe to rotation changes for building synchronized UI — debug overlays, companion shadows, linked animations:

<Parallax
  onRotationChange={({ rotateX, rotateY, isHovering }) => {
    console.log(`Tilt: ${rotateX.toFixed(1)}° × ${rotateY.toFixed(1)}°`);
  }}
>
  {children}
</Parallax>
Enter fullscreen mode Exit fullscreen mode

prefers-reduced-motion Support

The component automatically respects the OS-level reduced motion setting. All transitions, hover effects, spring animations, and touch/gyroscope interactions are disabled when active — no extra configuration needed.


Performance Rewrite

The internal architecture was rewritten from the ground up:

  • Native events + requestAnimationFrame: Replaced the useMouse hook (which triggered a React re-render on every pixel of mouse movement) with native onMouseMove/onTouchMove events throttled via requestAnimationFrame. Rotation updates are batched at 60fps with zero React re-renders during interaction.

  • GPU-friendly shadow updates: box-shadow CSS transitions — which are not GPU-accelerated and cause expensive repaints — are no longer applied during active hover. The shadow updates directly via RAF at 60fps. The CSS transition is only used on mouse leave for a smooth fade-out.

  • Mid-hover deactivation: If disabled or prefers-reduced-motion changes to true while the card is being hovered, the component now properly deactivates instead of continuing to process events.


Bug Fixes

  • Light effect persistence: The light gradient overlay no longer disappears from the DOM after mouse leave, preventing a flash when re-entering the card.
  • Background position conflict: Setting backgroundPosition without a backgroundImage no longer causes CSS shorthand/longhand conflicts with Mantine's bg prop in React 19.
  • Shadow during spring return: The dynamic shadow now follows the card rotation during spring return animation instead of snapping to zero instantly.
  • Spring parameter hot-swap: Changing springStiffness or springDamping mid-animation uses the new values immediately and resets accumulated velocity.
  • Gyroscope permission dedup: Rapid taps no longer trigger multiple permission prompts or register duplicate deviceorientation listeners.

Breaking Changes

1. New glare style selector

ParallaxStylesNames now includes 'glare' as a fourth selector. If you have exhaustive classNames or styles overrides:

// Before
<Parallax classNames={{ root: '...', content: '...', light: '...' }} />

// After — add the glare key
<Parallax classNames={{ root: '...', content: '...', light: '...', glare: '...' }} />
Enter fullscreen mode Exit fullscreen mode

2. New --parallax-radius CSS variable

The root element's border-radius is now controlled by a CSS variable. If you were overriding border-radius via styles, the CSS variable takes precedence — use the radius prop instead:

// Before
<Parallax style={{ borderRadius: 16 }}>

// After
<Parallax radius={16}>
Enter fullscreen mode Exit fullscreen mode

3. New exports and factory type changes

ParallaxFactory now includes vars (ParallaxCssVariables) and staticComponents ({ Layer: typeof ParallaxLayer }). New exports: ParallaxLayer, ParallaxLayerProps, ParallaxProvider, useParallaxContext, ParallaxContextValue, ParallaxCssVariables.


Migration from contentParallax to Parallax.Layer

The legacy contentParallax prop still works, but Parallax.Layer is the recommended approach:

// Before (legacy)
<Parallax contentParallax contentParallaxDistance={2}>
  <div>Child 1</div>
  <div>Child 2</div>
</Parallax>

// After (recommended)
<Parallax>
  <Parallax.Layer depth={2}>
    <div>Child 1</div>
  </Parallax.Layer>
  <Parallax.Layer depth={4}>
    <div>Child 2</div>
  </Parallax.Layer>
</Parallax>
Enter fullscreen mode Exit fullscreen mode

Parallax.Layer gives you explicit control over each element's depth and works with any children — no cloneElement limitations.


New Props at a Glance

Prop Type Default Description
springEffect boolean false Enable spring physics animation
springStiffness number 150 Spring snappiness
springDamping number 12 Spring bounce reduction
gyroscopeEnabled boolean false Enable device orientation tilt
gyroscopeSensitivity number 1 Gyroscope rotation multiplier
keyboardEnabled boolean false Enable arrow key interaction
keyboardStep number 5 Degrees per key press
shadowEffect boolean false Enable dynamic shadow
shadowColor MantineColor 'rgba(0,0,0,0.4)' Shadow color
shadowBlur number 30 Shadow blur radius
shadowOffset number 0.8 Shadow offset multiplier
glareEffect boolean false Enable glare reflection
glareColor MantineColor 'rgba(255,255,255,0.4)' Glare color
glareMaxOpacity number 0.4 Maximum glare opacity
glareSize number 30 Glare band size
glareOverlay boolean true Render glare above content
radius MantineRadius Border radius (theme tokens or px)
touchEnabled boolean true Enable touch interactions
hoverScale number 1 Scale factor on hover
resetOnLeave boolean true Reset rotation on leave
invertRotation boolean false Tilt away from cursor
maxRotation number Clamp rotation degrees
transitionDuration number 300 Transition time in ms
transitionEasing string 'ease-out' CSS easing function
onRotationChange function Rotation change callback

Getting Started

# Install or update
npm install @gfazioli/mantine-parallax@3
# or
yarn add @gfazioli/mantine-parallax@3
Enter fullscreen mode Exit fullscreen mode

Import the styles in your app entry point:

import '@gfazioli/mantine-parallax/styles.css';
Enter fullscreen mode Exit fullscreen mode

Basic usage with the new features:

import { Parallax } from '@gfazioli/mantine-parallax';

function InteractiveCard() {
  return (
    <Parallax
      radius="md"
      springEffect
      glareEffect
      shadowEffect
      p={24}
      bg="dark.7"
    >
      <Parallax.Layer depth={1}>
        <Text c="dimmed">Background layer</Text>
      </Parallax.Layer>
      <Parallax.Layer depth={3}>
        <Title c="white">Foreground layer</Title>
      </Parallax.Layer>
    </Parallax>
  );
}
Enter fullscreen mode Exit fullscreen mode

Links

Top comments (0)