Moving Border
A border that moves around the container. Perfect for making your buttons stand out.
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Add the following code in tailwind.config.js
file
const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
const {
default: flattenColorPalette,
} = require("tailwindcss/lib/util/flattenColorPalette");
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{ts,tsx}"],
darkMode: "class",
theme: {
// rest of the code
},
plugins: [
// rest of the code
addVariablesForColors,
],
};
// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
function addVariablesForColors({ addBase, theme }: any) {
let allColors = flattenColorPalette(theme("colors"));
let newVars = Object.fromEntries(
Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
);
addBase({
":root": newVars,
});
}
Copy the source code
components/ui/moving-border.tsx
"use client";
import React from "react";
import {
motion,
useAnimationFrame,
useMotionTemplate,
useMotionValue,
useTransform,
} from "framer-motion";
import { useRef } from "react";
import { cn } from "@/lib/utils";
export function Button({
borderRadius = "1.75rem",
children,
as: Component = "button",
containerClassName,
borderClassName,
duration,
className,
...otherProps
}: {
borderRadius?: string;
children: React.ReactNode;
as?: any;
containerClassName?: string;
borderClassName?: string;
duration?: number;
className?: string;
[key: string]: any;
}) {
return (
<Component
className={cn(
"bg-transparent relative text-xl h-16 w-40 p-[1px] overflow-hidden ",
containerClassName
)}
style={{
borderRadius: borderRadius,
}}
{...otherProps}
>
<div
className="absolute inset-0"
style={{ borderRadius: `calc(${borderRadius} * 0.96)` }}
>
<MovingBorder duration={duration} rx="30%" ry="30%">
<div
className={cn(
"h-20 w-20 opacity-[0.8] bg-[radial-gradient(var(--sky-500)_40%,transparent_60%)]",
borderClassName
)}
/>
</MovingBorder>
</div>
<div
className={cn(
"relative bg-slate-900/[0.8] border border-slate-800 backdrop-blur-xl text-white flex items-center justify-center w-full h-full text-sm antialiased",
className
)}
style={{
borderRadius: `calc(${borderRadius} * 0.96)`,
}}
>
{children}
</div>
</Component>
);
}
export const MovingBorder = ({
children,
duration = 2000,
rx,
ry,
...otherProps
}: {
children: React.ReactNode;
duration?: number;
rx?: string;
ry?: string;
[key: string]: any;
}) => {
const pathRef = useRef<any>();
const progress = useMotionValue<number>(0);
useAnimationFrame((time) => {
const length = pathRef.current?.getTotalLength();
if (length) {
const pxPerMillisecond = length / duration;
progress.set((time * pxPerMillisecond) % length);
}
});
const x = useTransform(
progress,
(val) => pathRef.current?.getPointAtLength(val).x
);
const y = useTransform(
progress,
(val) => pathRef.current?.getPointAtLength(val).y
);
const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
return (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
className="absolute h-full w-full"
width="100%"
height="100%"
{...otherProps}
>
<rect
fill="none"
width="100%"
height="100%"
rx={rx}
ry={ry}
ref={pathRef}
/>
</svg>
<motion.div
style={{
position: "absolute",
top: 0,
left: 0,
display: "inline-block",
transform,
}}
>
{children}
</motion.div>
</>
);
};
Props
Prop | Type | Description |
---|---|---|
borderRadius | string | Optional. Defines the border radius of the button. Default value is "1.75rem". |
children | React.ReactNode | Required. The content to be displayed inside the button. |
as | any | Optional. Defines the HTML element or React component that will be used for the button. Default is "button". |
containerClassName | string | Optional. Additional CSS classes to be applied to the button container. |
borderClassName | string | Optional. Additional CSS classes to be applied to the button border. |
duration | number | Optional. Duration for the moving border animation in milliseconds. Default is 2000. |
className | string | Optional. Additional CSS classes to be applied to the button. |
[key: string] | any | Optional. Any other props that should be passed to the button. |