Arrow Button
Outline button that fills on hover while its arrow slides through.
components/ui/arrow-button.tsx
"use client";
import { ArrowRight } from "lucide-react";
import type { ButtonHTMLAttributes } from "react";
export function ArrowButton({ children, className = "", ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
className={`group relative inline-flex items-center gap-3 overflow-hidden rounded-full border border-zinc-900 bg-white py-1.5 pl-5 pr-1.5 text-sm font-medium text-zinc-900 transition-colors duration-500 hover:text-white ${className}`}
{...props}
>
<span
aria-hidden
className="absolute inset-0 origin-right scale-x-0 bg-zinc-900 transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:origin-left group-hover:scale-x-100"
/>
<span className="relative">{children}</span>
<span className="relative grid size-8 place-items-center overflow-hidden rounded-full bg-zinc-900 text-white transition-colors duration-500 group-hover:bg-white group-hover:text-zinc-900">
<ArrowRight className="size-4 transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-[180%]" />
<ArrowRight className="absolute size-4 -translate-x-[180%] transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-0" />
</span>
</button>
);
}
export default function Demo() {
return (
<div className="flex flex-wrap items-center justify-center gap-4">
<ArrowButton>Book a demo</ArrowButton>
<ArrowButton className="border-blue-600 text-blue-600 [&>span:first-child]:bg-blue-600 [&>span:last-child]:bg-blue-600">
View pricing
</ArrowButton>
</div>
);
}
Installation
Install the dependencies
$ npm i motion lucide-reactCopy the source into
components/ui/arrow-button.tsxcomponents/ui/arrow-button.tsx"use client"; import { ArrowRight } from "lucide-react"; import type { ButtonHTMLAttributes } from "react"; export function ArrowButton({ children, className = "", ...props }: ButtonHTMLAttributes<HTMLButtonElement>) { return ( <button className={`group relative inline-flex items-center gap-3 overflow-hidden rounded-full border border-zinc-900 bg-white py-1.5 pl-5 pr-1.5 text-sm font-medium text-zinc-900 transition-colors duration-500 hover:text-white ${className}`} {...props} > <span aria-hidden className="absolute inset-0 origin-right scale-x-0 bg-zinc-900 transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:origin-left group-hover:scale-x-100" /> <span className="relative">{children}</span> <span className="relative grid size-8 place-items-center overflow-hidden rounded-full bg-zinc-900 text-white transition-colors duration-500 group-hover:bg-white group-hover:text-zinc-900"> <ArrowRight className="size-4 transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-[180%]" /> <ArrowRight className="absolute size-4 -translate-x-[180%] transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-0" /> </span> </button> ); } export default function Demo() { return ( <div className="flex flex-wrap items-center justify-center gap-4"> <ArrowButton>Book a demo</ArrowButton> <ArrowButton className="border-blue-600 text-blue-600 [&>span:first-child]:bg-blue-600 [&>span:last-child]:bg-blue-600"> View pricing </ArrowButton> </div> ); }Use it
The file exports the component and a
Demoshowing typical usage. Import whichever you need.