# How to Handle Ripple Effects in Material-UI Buttons Using useLazyRipple

> Optimize Material-UI button performance with useLazyRipple. Learn how this hook defers TouchRipple creation for faster initial renders and improved user experience.

- Repository: [MUI/material-ui](https://github.com/mui/material-ui)
- Tags: how-to-guide
- Published: 2026-02-26

---

**Material-UI's `useLazyRipple` hook defers the creation of the `TouchRipple` component until the first user interaction, reducing initial render overhead in applications with many buttons.**

The `useLazyRipple` hook is an internal performance optimization in the **mui/material-ui** repository that controls how ripple animations initialize in button components. By default, Material-UI buttons mount a `TouchRipple` instance immediately, which can create unnecessary DOM nodes and JavaScript overhead when rendering lists or grids containing dozens of interactive elements.

## Understanding the Ripple Architecture in Material-UI

Material-UI implements the ripple effect through a layered architecture centered around the **ButtonBase** component. When you render a `<Button>` or `<IconButton>`, you are actually using a composite built on top of `ButtonBase` located at [`packages/mui-material/src/ButtonBase/ButtonBase.tsx`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/ButtonBase/ButtonBase.tsx).

The visual ripple animation itself is handled by the **TouchRipple** component in [`packages/mui-material/src/ButtonBase/TouchRipple.tsx`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/ButtonBase/TouchRipple.tsx). This component manages the expanding circles and their CSS animations. By default, `ButtonBase` mounts `TouchRipple` immediately during the initial render, creating the ripple container in the DOM regardless of whether the user ever interacts with the button.

## How useLazyRipple Works

The `useLazyRipple` hook, implemented in [`packages/mui-material/src/ButtonBase/useLazyRipple.ts`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/ButtonBase/useLazyRipple.ts), solves the eager mounting problem through **lazy initialization**. Instead of creating the `TouchRipple` instance immediately, the hook returns an initialization function that remains dormant until the first user interaction.

The internal flow follows three distinct stages:

1. **Initial render** – `useLazyRipple` returns an `initRipple` callback and a `rippleAction` ref. No `TouchRipple` element exists in the DOM, reducing the initial render footprint.

2. **First interaction** – When the user triggers `mousedown` or `touchstart`, the `ButtonBase` event handler invokes `initRipple()`. The hook then creates the `TouchRipple` instance, inserts it into the DOM, and immediately forwards the ripple start request.

3. **Subsequent interactions** – The already-mounted `TouchRipple` component is reused for all future interactions, ensuring the initialization cost is paid only once per button instance.

## Implementing useLazyRipple in Your Components

Most developers do not need to manually implement `useLazyRipple` because Material-UI's standard components already utilize it internally. When you import `Button` from `@mui/material`, you are automatically benefiting from the lazy ripple optimization through the `ButtonBase` inheritance chain.

```tsx
import * as React from 'react';
import Button from '@mui/material/Button';

// This button automatically uses useLazyRipple via ButtonBase
export default function LazyRippleDemo() {
  return (
    <Button variant="contained" onClick={() => console.log('clicked')}>
      Click Me
    </Button>
  );
}

```

The lazy initialization occurs transparently in the background, reducing the DOM weight of large button grids without requiring any changes to your application code.

## Customizing and Disabling the Ripple Effect

For advanced use cases requiring direct control over the ripple lifecycle, Material-UI exports `unstable_useLazyRipple` from the `ButtonBase` module. This hook allows you to build custom button components with the same performance characteristics as the standard library.

The hook returns two critical references:
- **`initRipple`**: A callback function that triggers the creation of the `TouchRipple` instance
- **`rippleAction`**: A ref containing methods to control the ripple animation (start, stop, pulsate)

```tsx
import * as React from 'react';
import ButtonBase from '@mui/material/ButtonBase';
import { unstable_useLazyRipple } from '@mui/material/ButtonBase';

export default function CustomLazyRipple() {
  const rippleRef = React.useRef<any>(null);
  const { initRipple, rippleAction } = unstable_useLazyRipple(rippleRef);

  const handleMouseDown = (event: React.MouseEvent) => {
    // Initialise the ripple on first press
    initRipple();
    rippleAction.current?.start(event);
  };

  return (
    <ButtonBase
      onMouseDown={handleMouseDown}
      ref={rippleRef}
      // Disable default ripple to avoid double-initialisation
      disableRipple
    >
      Custom Button
    </ButtonBase>
  );
}

```

To completely disable the ripple effect without custom logic, pass the **`disableRipple`** prop to any button component. This prevents both the eager mounting and the lazy initialization of the `TouchRipple` component.

## Summary

- **`useLazyRipple`** is an internal performance hook in [`packages/mui-material/src/ButtonBase/useLazyRipple.ts`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/ButtonBase/useLazyRipple.ts) that defers `TouchRipple` creation until the first user interaction.
- Standard Material-UI buttons automatically benefit from this optimization through the `ButtonBase` component, requiring no code changes for most applications.
- The hook returns `initRipple` for triggering initialization and `rippleAction` for controlling the animation lifecycle, available as `unstable_useLazyRipple` for custom implementations.
- Use the **`disableRipple`** prop to completely suppress ripple effects when the animation is not desired.

## Frequently Asked Questions

### What is the performance benefit of using useLazyRipple?

The primary benefit is **reduced initial render overhead**. Without lazy initialization, every button mounts a `TouchRipple` component immediately, creating DOM nodes and JavaScript objects even for buttons that users never click. In applications with dense button grids or lists, `useLazyRipple` significantly reduces memory usage and improves Time-to-Interactive metrics by creating these elements only when needed.

### Is useLazyRipple stable for production use?

While the hook is used internally by stable components like `Button` and `IconButton`, the direct export **`unstable_useLazyRipple`** indicates that the hook's API surface may change in future releases without following the standard deprecation cycle. For production applications, rely on the stable component APIs (`Button`, `ButtonBase`) rather than importing the hook directly, unless you are prepared to handle potential breaking changes in minor version updates.

### How do I disable the ripple effect on specific buttons?

Pass the boolean prop **`disableRipple`** to any Material-UI button component. When `disableRipple={true}` is set, the component skips both eager and lazy initialization of the `TouchRipple` component, effectively removing the ripple animation. This prop is available on all components that inherit from `ButtonBase`, including `Button`, `IconButton`, `Tab`, and `ListItemButton`.

### Can I customize the ripple color or animation with useLazyRipple?

The `useLazyRipple` hook itself does not control visual styling; it only manages the **lifecycle and initialization** of the `TouchRipple` component. To customize the ripple appearance, use the **`TouchRippleProps`** prop on `ButtonBase` or override the theme's ripple styling. The `TouchRipple` component accepts props like `center` and classes for color customization, which you can pass through `ButtonBase` even when using the lazy initialization pattern.