Home / Snippets / Animation /

Wave path

Animate an element along a wave-shaped path using CSS offset-path with an SVG path definition — no JavaScript required.

New feature
animationno-js

Quick implementation

.wave-dot {
  offset-path: path('M 20,64 C 60,20 100,108 140,64 C 180,20 220,108 260,64 C 300,20 340,108 380,64');
  offset-distance: 0%;
  offset-rotate: auto;
  animation: wave-travel 2.5s linear infinite;
}

@keyframes wave-travel {
  to { offset-distance: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .wave-dot { animation: none; }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building CSS motion path animations.

Goal: Animate an element along a wave-shaped curved path using CSS
offset-path with an SVG path string — no JavaScript required.

Technical constraints:
- Use offset-path: path('M...C...') with cubic bezier SVG commands to
  define the wave shape.
- Animate offset-distance from 0% to 100% with @keyframes.
- Add offset-rotate: auto so the element faces its direction of travel.
- Use oklch() for all color values — no hex or rgba.
- Wrap the animation in @media (prefers-reduced-motion: reduce) and stop it.

Framework variant (pick one):
A) Vanilla CSS — a .wave-dot class applied to any element.
B) React component — accepts pathD (string, the SVG path data), duration
   (number, seconds), and color (string) props.

Edge cases to handle:
- The path coordinates are in the element's local coordinate space, not
  the viewport — position the container as position: relative and the
  animated element as position: absolute with top: 0; left: 0.
- If offset-rotate: auto causes unwanted flipping, use offset-rotate: 0deg
  to lock the element's orientation.
- Screen readers do not perceive CSS motion — no ARIA is needed for the
  animation itself, but the container should have a descriptive label if
  the motion conveys meaning.

Return CSS only (or a React component if variant B).

Why this matters in 2026

CSS offset-path motion paths landed in all major browsers in 2022 and replace JavaScript-driven animation libraries for curved-path effects. Previously, animating along a non-linear path required a JS library like GSAP to interpolate coordinates at every frame — adding kilobytes of script for what CSS can now do natively. In 2026, with full cross-browser support established, offset-path is the correct tool for any animation that follows a curve.

How offset-path and offset-distance work together

offset-path accepts a path() string using SVG path syntax. offset-distance (animatable from 0% to 100%) moves the element along that path. offset-rotate: auto rotates the element to face the direction of travel — removing it makes the element stay upright while sliding along the curve.

The cubic bezier commands (C) in the path definition produce the smooth wave shape: each C x1,y1 x2,y2 x,y command defines two control points and the endpoint for a curve segment. Three such segments join end-to-end to form a full sinusoidal wave across the container.

Because the animated element is positioned absolute with top: 0; left: 0, the path coordinates are interpreted relative to the element's own containing block — so the SVG path data you render as a visible guide and the offset-path string can be identical, keeping the visual and the animation perfectly in sync.

Accessibility and performance

Wrap the animation in @media (prefers-reduced-motion: reduce) and stop it completely — offset-path motion is vestibular-triggering for users with motion sensitivities. Setting offset-distance: 50% in the reduced-motion block keeps the element visible and centred on the path rather than disappearing.

offset-distance is composited by the browser (GPU layer), so it animates at 60 fps without touching layout. Avoid animating offset-path itself (the path definition) — only animate offset-distance. Changing the path string at runtime forces a style recalculation and defeats the compositing benefit.

Mark the decorative SVG guide path as aria-hidden="true" so screen readers skip it. If the animation conveys meaningful information (for example, showing a data flow direction), add an aria-label to the container describing what is happening.