Ripple

Expand a restrained ripple from the input coordinate for precise touch feedback.

Preview

Live preview

A circle expanding from the point of a tap, confirming the press.

Preview
Authoritative vocabulary

Definition and close-term distinctions

English definitions are preserved verbatim from Animation Vocabulary; translations and distinctions help you choose precisely.

Canonical termripple

Ripple

涟漪
Authoritative definition

A circle expanding from the point of a tap, confirming the press.

Chinese translation

圆形从轻触点向外扩散,用来确认按压操作。

Browse all 91 terms
Design contract

Decide before this motion ships

Purpose, frequency, trigger, timing, interruptibility, and gesture rules shape the implementation.

Purpose

Expand a restrained ripple from the actual input coordinate to confirm the touch point.

Frequency

Use for medium-frequency, larger touch targets; dense desktop controls can use lighter press feedback.

Trigger

Trigger on pointerdown, positioning the center from coordinates relative to the control.

Enter / exit

Expand and fade from the touch point, removing the ripple node afterward while the control stays stable.

Interruptibility

Allow repeated presses to finish independently while capping concurrent ripple nodes.

Gesture and keyboard

Keyboard activation uses the control center, and the ripple layer never intercepts pointer events.

Reduced-motion strategy

Remove expansion and keep an immediate background or outline change.

Output

Copy into your interface

Current parameters update visual-state CSS, semantic HTML, the required vanilla JS runtime, and an interaction prompt.

Updates with parameters
motion.css
.motion-demo.motion-demo--ripple {
  position: relative;
  display: grid;
  place-items: center;
  width: min(100%, 34rem);
  min-height: 15rem;
  padding: 2rem;
  overflow: hidden;
  color: #18181b;
  background: #fafafa;
  border: 1px solid #e4e4e7;
  border-radius: 12px;
  isolation: isolate;
}

.motion-demo.motion-demo--ripple *, .motion-demo.motion-demo--ripple *::before, .motion-demo.motion-demo--ripple *::after {
  box-sizing: border-box;
}

.motion-demo.motion-demo--ripple .motion-surface {
  position: relative;
  display: grid;
  gap: 0.65rem;
  width: min(100%, 18rem);
  padding: 1.25rem;
  background: #ffffff;
  border: 1px solid #e4e4e7;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 10px 28px rgb(0 0 0 / 7%);
}

.motion-demo.motion-demo--ripple .motion-line {
  display: block;
  width: 100%;
  height: 0.5rem;
  background: #e4e4e7;
  border-radius: 999px;
}

.motion-demo.motion-demo--ripple .motion-line:last-child { width: 62%; }

.motion-demo.motion-demo--ripple .motion-button {
  position: relative;
  min-height: 2.75rem;
  padding: 0.75rem 1.1rem;
  color: white;
  font: inherit;
  font-weight: 700;
  background: #18181b;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--ripple .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }

.motion-demo.motion-demo--ripple .motion-action-row {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.motion-demo.motion-demo--ripple .motion-replay-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--ripple .motion-pausable-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--ripple button.motion-surface {
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.motion-demo.motion-demo--ripple .motion-replay-button,
.motion-demo.motion-demo--ripple .motion-undo-button,
.motion-demo.motion-demo--ripple .motion-dismiss-button,
.motion-demo.motion-demo--ripple .motion-pause-button {
  min-height: 2.75rem;
  padding: 0.65rem 0.9rem;
  color: #18181b;
  font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
  background: #ffffff;
  border: 1px solid #d4d4d8;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--ripple .motion-replay-button:focus-visible,
.motion-demo.motion-demo--ripple .motion-undo-button:focus-visible,
.motion-demo.motion-demo--ripple .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--ripple .motion-pause-button:focus-visible,
.motion-demo.motion-demo--ripple [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--ripple [data-swipe-target]:focus-visible {
  outline: 3px solid rgb(37 99 235 / 32%);
  outline-offset: 3px;
}

.motion-demo.motion-demo--ripple .motion-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.motion-demo.motion-demo--ripple .motion-ripple-ink {
  position: absolute;
  width: 12px;
  height: 12px;
  pointer-events: none;
  background: rgb(255 255 255 / 72%);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0.95);
  opacity: 0;
}

.motion-demo.motion-demo--ripple .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--ripple .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 8px; }
.motion-demo.motion-demo--ripple .motion-state--to { background: #18181b; color: white; }

.motion-demo.motion-demo--ripple .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--ripple .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--ripple .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--ripple svg { width: min(100%, 20rem); height: auto; }

.motion-demo.motion-demo--ripple .motion-button { position: relative; overflow: hidden; isolation: isolate; }
.motion-demo.motion-demo--ripple .motion-button > :not(.motion-ripple-ink) { position: relative; z-index: 1; }

@keyframes motion-ripple-reduced-fade {
  from { opacity: 0.84; }
  to { opacity: 1; }
}

.force-reduced-motion .motion-demo.motion-demo--ripple {
  animation: motion-ripple-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}

.force-reduced-motion .motion-demo.motion-demo--ripple *,
.force-reduced-motion .motion-demo.motion-demo--ripple *::before,
.force-reduced-motion .motion-demo.motion-demo--ripple *::after {
  animation: none !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-property: opacity, color, background-color, border-color !important;
  transition-duration: 160ms !important;
  transition-delay: 0ms !important;
  scroll-behavior: auto !important;
}

.force-reduced-motion .motion-demo.motion-demo--ripple .motion-button,
.force-reduced-motion .motion-demo.motion-demo--ripple [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--ripple [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--ripple [data-spring-target] {
  transform: none !important;
}

.force-reduced-motion .motion-demo.motion-demo--ripple .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--ripple .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--ripple .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  .motion-demo.motion-demo--ripple {
    animation: motion-ripple-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
  }

  .motion-demo.motion-demo--ripple *, .motion-demo.motion-demo--ripple *::before, .motion-demo.motion-demo--ripple *::after {
    animation: none !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-property: opacity, color, background-color, border-color !important;
    transition-duration: 160ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .motion-demo.motion-demo--ripple .motion-button,
  .motion-demo.motion-demo--ripple [data-reorder-item],
  .motion-demo.motion-demo--ripple [data-swipe-target],
  .motion-demo.motion-demo--ripple [data-spring-target] {
    transform: none !important;
  }

  .motion-demo.motion-demo--ripple .motion-path { stroke-dashoffset: 0 !important; }
  .motion-demo.motion-demo--ripple .motion-word--from { opacity: 0 !important; visibility: hidden; }
  .motion-demo.motion-demo--ripple .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}
Review notes

Pre-delivery review

  • Check clipping against border radius and overflow.
  • Keep the ripple visible without obscuring the label.
Accessibility

Keep information accessible

Reduced motion

Remove expansion and keep an immediate background or outline change.

Parameters

Parameter reference

ParameterRange / optionsDefaultPurpose
duration80ms–1200ms160msControls the time required for one motion cycle.
size120%–360%220%Adjusts ripple size.
opacity8%–40%24%Adjusts ripple opacity.