npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

material-design-ripple

v2.0.1

Published

Zero-dependency material ripple animation library

Readme

material-design-ripple

A single function that adds the material ripple animation to any element you point at. No dependencies, ~2 kB gzipped.

import { ripple } from 'material-design-ripple'

ripple('.button, .card')

Demo

Install

npm install material-design-ripple

Usage

Pass a selector and the effect is delegated from the document, so elements rendered later are covered too -- no need to re-run anything after a render:

import { ripple } from 'material-design-ripple'

const handle = ripple('.button')

document.body.append(newButton) // ripples, without another ripple() call
handle.destroy() // removes every listener, rule and live wave

Elements and node lists are bound directly instead, and the two can be mixed:

ripple(document.querySelector('#cta'))
ripple(document.querySelectorAll('.chip'))
ripple(['.card', myButton])

With a plain script tag:

<script src="https://unpkg.com/material-design-ripple/dist/material-ripple.min.js"></script>
<script>
  MaterialRipple.ripple('.button')
</script>

Options

ripple('.icon-button', { center: true, color: 'currentColor' })

| Option | Default | What it does | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | color | 'auto' | 'auto' composites the background behind the host and picks black or white. Any CSS colour works, including 'currentColor'. | | opacity | 0.3 | Opacity the wave holds while pressed. | | center | false | Emanate from the centre instead of the pointer. Right for icon buttons and FABs. | | duration | 350 | Expansion duration, in ms. | | fadeDuration | 400 | Fade-out duration once released, in ms. | | keyboard | true | Also ripple on Space / Enter activation, centred. | | clip | true | Applies position: relative and overflow: hidden to hosts for you. |

Notes

The public API is ripple and nothing else. Everything under src/ beyond the . export is internal and can change in a patch release. Changes are recorded in CHANGELOG.md.

Press and release are separate phases. The wave expands on press and holds until the pointer lifts, then fades -- so a long press stays lit instead of draining away under your finger. Release is tracked on window, so dragging off the element before letting go still ends the ripple.

Clipping is handled for you. clip: true installs one :where(...) rule per call, which has zero specificity: any declaration of your own overrides it. Set clip: false to opt out entirely.

Colour is measured, not guessed. color: 'auto' walks up the ancestors compositing background colours until it has an opaque one, so translucent surfaces resolve to what you actually see. Colours outside sRGB (oklch(), lab(), color(display-p3 ...)) are skipped rather than misread, and the next ancestor is consulted instead.

Reduced motion is respected. With prefers-reduced-motion: reduce the wave appears without travelling, and still fades on release, so the press keeps its feedback without the movement.

One wave per press. Rapid presses overlap rather than cutting each other off, and multi-touch gives each pointer its own wave.

A <span> is appended to each host. It is aria-hidden and out of flow, but it does become the host's last child -- so a :last-child selector aimed at your own children will no longer match. This is the same trade-off the Material Components implementation makes.

Nested hosts. With delegation the innermost matching ancestor wins, so a button inside a rippling card ripples alone. Unlike pre-1.0 this is done without stopPropagation(), so your own pointerdown listeners still fire.

Two ripple() calls matching the same element give it two waves. Scope your selectors so they do not overlap, or pass options to a single call.

Selectors are validated when you call ripple(). An invalid one throws a TypeError naming it, rather than failing later on each press. Do not build a selector out of untrusted input: it is validated, not sanitised, and a selector API is the wrong place to put user data.

SSR is safe to import; ripple() itself needs a DOM, so call it on the client.

Migrating from 0.1.x

set() is gone. It is a straight rename, plus a handle you can now hold on to:

- import { set } from 'material-design-ripple'
- set(['.card', '.button'])
+ import { ripple } from 'material-design-ripple'
+ ripple(['.card', '.button'])

Three behaviours changed underneath, all of them deliberate:

  • Presses hold until release instead of fading from the moment you press.
  • stopPropagation() is no longer called, so your own pointerdown listeners on ancestors fire again.
  • Hosts are clipped for you, so you can drop the overflow: hidden you were keeping around for the effect. Pass clip: false to go back to doing it yourself.

Selectors are also delegated now rather than resolved once, which is why elements rendered after the call are covered. If you were re-running set() after every render, you can stop.

Developing

npm run dev

| Script | Does | | -------------------- | --------------------------------- | | npm run dev | Serves the demo with hot reload | | npm test | Runs the test suite | | npm run build | Builds the library into dist/ | | npm run build:demo | Builds the demo site into site/ | | npm run format | Formats everything with Prettier |

License

MIT © Viola Péter