Border Trail
sora-ui/border-trailFreeComponent
Animated border spotlight that travels around a rounded container, powered by Motion.
Install it
npx shadcn@latest add https://ui.soralabs.io.vn/r/border-trail.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { motion, type Transition, useReducedMotion } from "motion/react";5import { type CSSProperties, useEffect, useRef } from "react";67export interface BorderTrailProps {8 className?: string;9 onAnimationComplete?: () => void;10 size?: number;11 style?: CSSProperties;12 transition?: Transition;13}1415const DEFAULT_TRANSITION: Transition = {16 repeat: Number.POSITIVE_INFINITY,17 duration: 5,18 ease: "linear",19};2021function getReducedMotionCompletionMs(22 duration: number,23 delay: number,24 repeat: number | undefined,25 repeatDelay: number26): number | null {27 if (28 repeat === Number.POSITIVE_INFINITY ||29 repeat === Number.NEGATIVE_INFINITY30 ) {31 return null;32 }3334 // Motion: repeat N = initial play + N additional iterations.35 const iterationCount =36 typeof repeat === "number" && repeat > 0 ? repeat + 1 : 1;37 const betweenRepeatDelays =38 typeof repeat === "number" && repeat > 0 ? repeat * repeatDelay : 0;3940 return (delay + duration * iterationCount + betweenRepeatDelays) * 1000;41}4243export function BorderTrail({44 className,45 size = 60,46 transition,47 onAnimationComplete,48 style,49}: BorderTrailProps) {50 const prefersReducedMotion = useReducedMotion();51 const onAnimationCompleteRef = useRef(onAnimationComplete);52 onAnimationCompleteRef.current = onAnimationComplete;5354 const resolvedTransition = { ...DEFAULT_TRANSITION, ...transition };55 const duration =56 typeof resolvedTransition.duration === "number"57 ? resolvedTransition.duration58 : 5;59 const delay =60 typeof resolvedTransition.delay === "number" ? resolvedTransition.delay : 0;61 const repeat = resolvedTransition.repeat;62 const repeatDelay =63 typeof resolvedTransition.repeatDelay === "number"64 ? resolvedTransition.repeatDelay65 : 0;6667 useEffect(() => {68 if (!prefersReducedMotion) {69 return;70 }7172 const completionMs = getReducedMotionCompletionMs(73 duration,74 delay,75 repeat,76 repeatDelay77 );78 if (completionMs === null) {79 return;80 }8182 const timeoutId = window.setTimeout(83 () => onAnimationCompleteRef.current?.(),84 completionMs85 );86 return () => window.clearTimeout(timeoutId);87 }, [prefersReducedMotion, duration, delay, repeat, repeatDelay]);8889 if (prefersReducedMotion) {90 // className/style style the small moving square (glow shadows, gradient91 // fills sized for `size`px) — reusing them here would stretch a92// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from Sora UI
All 97 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | Sora UI | Components | Free | 2 deps | |
| Bottom Sheetbottom-sheet | Sora UI | Components | Free | 2 deps | |
| Cursor Bubblecursor-bubble | Sora UI | Components | Free | 2 deps | |
| Cursor Trail Revealcursor-trail-reveal | Sora UI | Components | Free | 1 dep | |
| Custom Cursorcustom-cursor | Sora UI | Components | Free | 2 deps | |
| Accordion Demodemo-accordion | Sora UI | Components | Free | no deps | |
| Border Trail Demodemo-border-trail | Sora UI | Components | Free | no deps | |
| Border Trail Loading Card Demodemo-border-trail-loading | Sora UI | Components | Free | no deps |
