Fluid Cursor
godui/fluid-cursorFreeComponent
A spring-lagged blend-mode cursor that trails the pointer and swells over interactive elements.
Install it
npx shadcn@latest add https://godui.design/r/fluid-cursor.jsonSource
1"use client";23import * as React from "react";4import { createPortal } from "react-dom";56export type FluidCursorProps = {7 /** Diameter of the leading dot in pixels. Defaults to `18`. */8 size?: number;9 /** Dot color. With `blend` on, `white` reads as a clean invert. Defaults to `white`. */10 color?: string;11 /** Invert whatever is underneath via `mix-blend-mode: difference`. Defaults to `true`. */12 blend?: boolean;13 /** Render a soft lagging smear behind the dot. Defaults to `true`. */14 trail?: boolean;15 /**16 * Scope the cursor to an element instead of the whole page. The element must17 * be positioned (e.g. `relative`). Omit for a global cursor.18 */19 containerRef?: React.RefObject<HTMLElement | null>;20 /** Selector for elements that enlarge the cursor on hover. */21 interactiveSelector?: string;22 className?: string;23};2425const FluidCursor = ({26 size = 18,27 color = "white",28 blend = true,29 trail = true,30 containerRef,31 interactiveSelector = "a, button, [role='button'], [data-cursor]",32 className,33}: FluidCursorProps) => {34 const dotRef = React.useRef<HTMLDivElement>(null);35 const trailRef = React.useRef<HTMLDivElement>(null);36 const [enabled, setEnabled] = React.useState(false);37 const [target, setTarget] = React.useState<HTMLElement | null>(null);38 const motion = React.useRef({39 x: 0,40 y: 0,41 tx: 0,42 ty: 0,43 sx: 0,44 sy: 0,45 hover: 0,46 thover: 0,47 inside: 0,48 seen: false,49 });5051 React.useEffect(() => {52 if (typeof window.matchMedia !== "function") return;53 const fine = window.matchMedia("(pointer: fine)").matches;54 const reduce = window.matchMedia(55 "(prefers-reduced-motion: reduce)",56 ).matches;57 setEnabled(fine && !reduce);58 setTarget(containerRef ? containerRef.current : document.body);59 }, [containerRef]);6061 React.useEffect(() => {62 if (!enabled || !target) return;63 const s = motion.current;64 const scoped = Boolean(containerRef);65 const host: HTMLElement | Window = scoped ? target : window;6667 // The follower idles once it catches up to the pointer, so stop the rAF when68 // everything has settled (or the tab is hidden) and only restart it on real69 // pointer activity — no forever-spinning loop writing identical transforms.70 let raf = 0;71 const SETTLE = 0.01;72 const settled = () =>73 Math.abs(s.tx - s.x) < SETTLE &&74 Math.abs(s.ty - s.y) < SETTLE &&75 Math.abs(s.tx - s.sx) < SETTLE &&76 Math.abs(s.ty - s.sy) < SETTLE &&77// … truncated
What it pulls in
Other registry items
Files it writes
More from godui
All 105 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | godui | Components | Free | 1 dep | |
| Agent Flowagent-flow | godui | Components | Free | 1 dep | |
| Agent Timelineagent-timeline | godui | Components | Free | 1 dep | |
| Animated Beamanimated-beam | godui | Components | Free | 1 dep | |
| Animated Testimonialsanimated-testimonials | godui | Components | Free | 1 dep | |
| Animated Tooltipanimated-tooltip | godui | Components | Free | 1 dep | |
| App Showcaseapp-showcase | godui | Components | Free | 1 dep | |
| Aurora Textaurora-text | godui | Components | Free | no deps |
