Mantine Reflection

Logo

@gfazioli/mantine-reflection

A Mantine component that adds a reflection effect to its children.

Installation

yarn add @gfazioli/mantine-reflection

After installation import package styles at the root of your application:

import '@gfazioli/mantine-reflection/styles.css';

You can import styles within a layer @layer mantine-reflection by importing @gfazioli/mantine-reflection/styles.layer.css file.

import '@gfazioli/mantine-reflection/styles.layer.css';

Usage

The Reflection component simply wraps the content to be reflected.

test
Ripple strength
Ripple frequency
Ripple speed
Ripple octaves
Ripple seed
Reflection distance
Reflection opacity
Reflection blur
Reflection start
Reflection end
Reflection stretch
Shadow offset
Shadow opacity
Shadow blur
Shadow size
Shadow scale x
Shadow scale y
Shadow color
import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  return (
    <Reflection rippleStrength={30} rippleFrequency={0.025}>
      <img width={150} height={150} style={{ display: 'block', borderRadius: '50%', objectFit: 'cover' }} alt="test" src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png"
      />
    </Reflection>
  );
}

Reflection Blur: The reflectionBlur prop adds a blur effect to the reflection. The blur is automatically padded to prevent edge clipping — no manual padding needed.

The Image tag

The img tag requires to be in display: block in order to avoid padding below it. For this reason, a style has been added to force the behavior.

test
test
import { Reflection } from '@gfazioli/mantine-reflection';
import { Group } from '@mantine/core';

function Demo() {
  return (
    <Group mt="5%" gap={100} justify="center">
      <Reflection>
        <img
          width={150}
          height={150}
          style={{ display: 'block', objectFit: 'cover' }}
          alt="test"
          src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png"
        />
      </Reflection>

      <Reflection>
        <img width={150} height={150} style={{ objectFit: 'cover' }} alt="test" src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png" />
      </Reflection>
    </Group>
  );
}

Responsive props

Several props support responsive values using Mantine breakpoint objects — the same pattern used by Mantine core components like SimpleGrid. Under the hood, CSS media queries are generated via InlineStyles, so there are no React re-renders on resize.

The following props accept responsive breakpoint objects:

PropDescription
reflectionDistanceDistance between the original element and the reflection
reflectionOpacityOpacity of the reflection (e.g., reduce on mobile)
reflectionBlurBlur applied to the reflection (e.g., softer on small screens)
shadowSizeShadow size below the original element
<Reflection
  reflectionDistance={{ base: 0, sm: 8, md: 16 }}
  reflectionOpacity={{ base: 0.15, sm: 0.25, md: 0.4 }}
  reflectionBlur={{ base: 2, sm: 1, md: 0 }}
  shadowSize={{ base: 5, sm: 8, md: 12 }}
>
  <img ... />
</Reflection>

Each prop also accepts a static value as before — responsive objects are entirely optional.

responsive
import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  return (
    <Reflection
      reflectionDistance={{ base: 0, sm: 8, md: 16 }}
      reflectionOpacity={{ base: 0.15, sm: 0.25, md: 0.4 }}
      reflectionBlur={{ base: 2, sm: 1, md: 0 }}
      shadowSize={{ base: 5, sm: 8, md: 12 }}
    >
      <img
        width={200}
        height={200}
        style={{ display: 'block', borderRadius: 8, objectFit: 'cover' }}
        alt="responsive"
        src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-6.png"
      />
    </Reflection>
  );
}

Components

You may use the Reflection component to reflect other components.

import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  return (
    <Reflection>
      <Button>Hello World!</Button>
    </Reflection>
  );
}

Example: Complex components

For complex components, you may need to disable certain components to prevent the mirrored version from functioning as a copy. In this example, we used the prop disableChildren for the right component. When enabled, all interactive elements in the reflection are set to disabled with tabIndex={-1}, preventing both mouse and keyboard interaction. The reflection is also marked as aria-hidden for screen readers.

test
test
import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  <Group mt="5%" gap={120} justify="center">
    <Reflection>
      <Paper withBorder w={230} p={16}>
        <Stack>
          <img
            style={{
              borderRadius: '8px',
              width: '100%',
              height: 150,
              objectFit: 'cover',
            }}
            alt="test"
            src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-3.png"
          />
          <Input placeholder="Click and press TAB 2 times" />
          <Button>Cancel</Button>
        </Stack>
      </Paper>
    </Reflection>
    <Reflection disableChildren>
      <Paper withBorder w={230} p={16}>
        <Stack>
          <img
            style={{
              borderRadius: '8px',
              width: '100%',
              height: 150,
              objectFit: 'cover',
            }}
            alt="test"
            src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-3.png"
          />
          <Input placeholder="Your name" />
          <Button>Cancel</Button>
        </Stack>
      </Paper>
    </Reflection>
  </Group>
  );
}

Water ripple effect

Set ripple to add a water distortion effect to the reflection using SVG filters (feTurbulence + feDisplacementMap). Configure the effect with:

  • rippleStrength (1-50, default 4) — distortion intensity
  • rippleFrequency (default 0.01) — wave density, higher = more waves
  • rippleSpeed (0-10, default 3) — animation speed, higher = faster, 0 for static
  • rippleOctaves (1-4, default 1) — noise detail level
  • rippleSeed (default 1) — seed for reproducible patterns
ripple
import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  return (
    <Reflection ripple rippleStrength={8} rippleFrequency={0.015} rippleSpeed={4}>
      <img
        width={200}
        height={200}
        style={{ display: 'block', borderRadius: 8, objectFit: 'cover' }}
        alt="ripple"
        src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-4.png"
      />
    </Reflection>
  );
}

Dark mode shadow

Set shadowColor="auto" to automatically switch the shadow color between black (light mode) and white (dark mode). This uses CSS light-dark() for seamless theme adaptation.

auto shadow
import { Reflection } from '@gfazioli/mantine-reflection';

function Demo() {
  return (
    <Reflection shadowColor="auto">
      <img
        width={150}
        height={150}
        style={{ display: 'block', borderRadius: '50%', objectFit: 'cover' }}
        alt="auto shadow"
        src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-5.png"
      />
    </Reflection>
  );
}

Accessibility

The reflection is purely decorative:

  • The reflection div is marked with aria-hidden="true" and role="presentation"
  • When disableChildren is enabled, all interactive elements are set to disabled with tabIndex={-1}
  • The reflectionBlur filter is automatically disabled when the user has prefers-reduced-motion enabled in their OS settings