
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.
<ParallaxspringEffectspringStiffness={150} // higher = snappierspringDamping={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.
<ParallaxglareEffectglareColor="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.
<ParallaxshadowEffectshadowColor="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.
<ParallaxgyroscopeEnabledgyroscopeSensitivity={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.
<ParallaxkeyboardEnabledkeyboardStep={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:
<ParallaxresetOnLeave={false} // card holds its tilt after mouse leaveinvertRotation // card tilts away from cursormaxRotation={15} // clamp rotation to ±15 degreeshoverScale={1.05} // subtle scale-up on hover/>Enter fullscreen mode Exit fullscreen mode
Transition Customization
Control the transition timing for enter/leave animations:
<ParallaxtransitionDuration={300} // mstransitionEasing="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:
<ParallaxonRotationChange={({ 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
useMousehook (which triggered a React re-render on every pixel of mouse movement) with nativeonMouseMove/onTouchMoveevents throttled viarequestAnimationFrame. Rotation updates are batched at 60fps with zero React re-renders during interaction.GPU-friendly shadow updates:
box-shadowCSS 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
disabledorprefers-reduced-motionchanges totruewhile 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
backgroundPositionwithout abackgroundImageno longer causes CSS shorthand/longhand conflicts with Mantine'sbgprop 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
springStiffnessorspringDampingmid-animation uses the new values immediately and resets accumulated velocity.Gyroscope permission dedup: Rapid taps no longer trigger multiple permission prompts or register duplicate
deviceorientationlisteners.
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 |
|---|---|---|---|
|
|
| Enable spring physics animation |
|
|
| Spring snappiness |
|
|
| Spring bounce reduction |
|
|
| Enable device orientation tilt |
|
|
| Gyroscope rotation multiplier |
|
|
| Enable arrow key interaction |
|
|
| Degrees per key press |
|
|
| Enable dynamic shadow |
|
|
| Shadow color |
|
|
| Shadow blur radius |
|
|
| Shadow offset multiplier |
|
|
| Enable glare reflection |
|
|
| Glare color |
|
|
| Maximum glare opacity |
|
|
| Glare band size |
|
|
| Render glare above content |
|
| — | Border radius (theme tokens or px) |
|
|
| Enable touch interactions |
|
|
| Scale factor on hover |
|
|
| Reset rotation on leave |
|
|
| Tilt away from cursor |
|
| — | Clamp rotation degrees |
|
|
| Transition time in ms |
|
|
| CSS easing 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
Comments (0)
Login to post a comment.