Skip to content
Components

Ripple Field

Ripple Field adds a distinctive visual surface to your composition.

Open ↗

Make it yours.

Install the editable source and its dependencies into your React + Tailwind project.

Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/ripple-field.json

Use it

Scroll horizontally for long lines
"use client";
import {RippleField} from '@/components/jez-ui/ui/ripple-field';

export default function Example(){

return <><RippleField/></>;
}

Props & types

Scroll horizontally for long lines
{ className?: string }

Source

View 2 source files

registry/ui/ripple-field.tsx

Scroll horizontally for long lines
"use client";
import * as React from "react";
import { cn } from "./utils";
export function RippleField({ className }: { className?: string }) {
  const [ripples, setRipples] = React.useState<
    { id: number; x: number; y: number }[]
  >([]);
  return (
    <button
      aria-label="Create a ripple"
      className={cn(
        "relative block h-64 w-full overflow-hidden rounded-xl bg-muted",
        className,
      )}
      onClick={(e) => {
        const r = e.currentTarget.getBoundingClientRect();
        setRipples((v) => [
          ...v.slice(-8),
          {
            id: performance.now(),
            x: e.detail ? e.clientX - r.left : r.width / 2,
            y: e.detail ? e.clientY - r.top : r.height / 2,
          },
        ]);
      }}
    >
      <span className="relative z-10 text-sm">
        Click anywhere. Make a little wave.
      </span>
      {ripples.map((r) => (
        <span
          key={r.id}
          onAnimationEnd={() =>
            setRipples((v) => v.filter((x) => x.id !== r.id))
          }
          aria-hidden="true"
          className="pointer-events-none absolute size-24 rounded-full border border-primary"
          style={{
            left: r.x - 48,
            top: r.y - 48,
            animation: "jez-ripple 1.2s ease-out forwards",
          }}
        />
      ))}
    </button>
  );
}

registry/ui/utils.ts

Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
  return twMerge(clsx(values));
}

Dependencies

clsx@2.1.1 · tailwind-merge@3.5.0

Accessibility & behaviour

Respect reduced-motion preferences. Decorative effects must not carry essential information. WebGL scenes include a static fallback and only render while visible.

Read the accessibility and performance guide →