CoreInteractive Buttons and Controlsno dependencies

VelocityButton

Interactive button with a directional energy trail and perimeter glow that responds to pointer velocity without expensive particle emitters.

Preview

Customize

4 props
0.45
260

Installation

Copies the component source and its CSS Module into your project.

npx motionfoundry add velocity-button

Writes components/ui/velocity-button.tsx and components/styles/velocity-button.module.css.

Usage

Updates live as you change the controls — only props that differ from the defaults are included.

example.tsx
1import { VelocityButton } from "@motionfoundry/ui";2 3export function Example() {4  return (5    <VelocityButton />6  );7}

Props

PropTypeDefault
label

Button text label.

string'Execute Edge Deployment'
trailColor

Color of the velocity-reactive energy trail.

string'#38bdf8'
trailIntensity

Maximum opacity multiplier of the energy trail.

number0.45
decayMs

Trail fade-out duration in milliseconds.

number260

Accessibility

Updates CSS custom properties directly on the DOM node for zero React re-renders during pointer movement and disables the directional trail when prefers-reduced-motion is enabled.

Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.

Source

components/ui/velocity-button.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/velocity-button.module.css";5 6export interface VelocityButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {7  /** Button label */8  label?: string;9  /** Energy trail color */10  trailColor?: string;11  /** Maximum opacity multiplier (0.15 to 0.85) */12  trailIntensity?: number;13  /** Decay duration in milliseconds */14  decayMs?: number;15}16 17export function VelocityButton({18  children,19  label = "Execute Edge Deployment",20  trailColor = "#38bdf8",21  trailIntensity = 0.45,22  decayMs = 260,23  className = "",24  ...rest25}: VelocityButtonProps) {26  const btnRef = React.useRef<HTMLButtonElement>(null);27  const lastPoint = React.useRef<{ x: number; y: number; t: number } | null>(null);28 29  const handlePointerMove = (e: React.PointerEvent<HTMLButtonElement>) => {30    const el = btnRef.current;31    if (!el) return;32    const rect = el.getBoundingClientRect();33    const x = e.clientX - rect.left;34    const y = e.clientY - rect.top;35    const now = performance.now();36 37    let stretch = 1;38    let angle = 0;39    if (lastPoint.current) {40      const dx = x - lastPoint.current.x;41      const dy = y - lastPoint.current.y;42      const dt = Math.max(8, now - lastPoint.current.t);43      const speed = Math.min(2.4, Math.hypot(dx, dy) / dt);44      stretch = 1 + speed * 0.65;45      angle = Math.atan2(dy, dx) * (180 / Math.PI);46    }47    lastPoint.current = { x, y, t: now };48 49    el.style.setProperty("--mf-vel-x", `${x}px`);50    el.style.setProperty("--mf-vel-y", `${y}px`);51    el.style.setProperty("--mf-vel-stretch", stretch.toFixed(2));52    el.style.setProperty("--mf-vel-angle", `${angle.toFixed(1)}deg`);53    el.style.setProperty("--mf-vel-opacity", String(trailIntensity));54  };55 56  const handlePointerLeave = () => {57    lastPoint.current = null;58    btnRef.current?.style.setProperty("--mf-vel-opacity", "0");59  };60 61  return (62    <button63      ref={btnRef}64      type="button"65      onPointerMove={handlePointerMove}66      onPointerLeave={handlePointerLeave}67      style={68        {69          "--mf-vel-color": trailColor,70          "--mf-vel-decay": `${decayMs}ms`,71        } as React.CSSProperties72      }73      className={`${styles.button} ${className}`.trim()}74      {...rest}75    >76      <span aria-hidden="true" className={styles.energyTrail} />77      <span className={styles.content}>{children ?? label}</span>78    </button>79  );80}