A call to action whose arrow slides out on hover while a fresh one slides in from the left. Pure CSS, solid or outline.
Customize
Usage
import { ArrowButton } from "@/components/motionkit/arrow-button";
<ArrowButton>Get started</ArrowButton>Motion
The exact curves this component runs, plotted with Motion's own maths.
Arrow swap
Hover
The main call to action on SaaS landing pages, pricing cards and sign-up flows, and the quieter outline version beside it.
Inside a small disc sit two identical arrows: one in view and one waiting just out of sight to the left. On hover, a CSS transform moves the visible arrow out to the right while the waiting one slides into its place, and the disc's overflow hides the hand-over. The label nudges slightly left at the same time. It's all group-hover classes, so it needs no JavaScript and can be a server component.
Works in the App Router as is. Requires Tailwind CSS v4.
One command adds the file and installs its dependencies. Needs a components.json (run npx shadcn@latest init once if you don't have one).
npx shadcn@latest add https://www.motionkitui.com/r/arrow-button.jsonnpm install motionSave as components/motionkit/arrow-button.tsx.
import type { ComponentProps } from "react";
type ArrowButtonProps = ComponentProps<"button"> & {
/** "solid" for the main call to action, "outline" for a quieter one beside it. */
variant?: "solid" | "outline";
};
const Arrow = ({ className = "" }: { className?: string }) => (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={`absolute size-3.5 transition-transform duration-300 ease-[cubic-bezier(0.65,0,0.35,1)] motion-reduce:transition-none ${className}`}>
<path d="M3 8h10M9 4l4 4-4 4" />
</svg>
);
/**
* A call to action whose arrow slides out to the right on hover while a fresh one slides in from the left,
* inside a small disc. Pure CSS, so it works without JavaScript and costs nothing.
*/
export function ArrowButton({ variant = "solid", className = "", children, ...props }: ArrowButtonProps) {
const styles =
variant === "solid"
? "bg-[#0E7C86] text-white hover:bg-[#0B6770] [--disc:rgb(255_255_255/0.18)]"
: "border border-white/15 text-[#F6F1E8] hover:border-white/30 [--disc:rgb(95_174_180/0.18)]";
return (
<button
{...props}
className={`group inline-flex items-center gap-3 rounded-full py-2 pl-5 pr-2 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#5FAEB4] disabled:pointer-events-none disabled:opacity-50 ${styles} ${className}`}
>
<span className="transition-transform duration-300 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:-translate-x-0.5 motion-reduce:transition-none">{children}</span>
<span aria-hidden className="relative flex size-7 items-center justify-center overflow-hidden rounded-full bg-[var(--disc)]">
<Arrow className="group-hover:translate-x-6" />
<Arrow className="-translate-x-6 group-hover:translate-x-0" />
</span>
</button>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "outline" | "solid" | Main call to action, or a quieter one. |
| children | ReactNode | — | The label. |
| ...props | ButtonHTMLAttributes | — | Anything a <button> accepts, such as onClick or type. |
How we audit: the WCAG 2.2 animation checklist and respecting reduced motion in React.
Yes. The file starts with "use client", so you can import it straight into a Server Component page or layout. It works in Vite as is, and in Astro as a React island.
Nothing beyond React and Tailwind CSS v4. There is no MotionKit package and no Tailwind config to add.
Yes. Run npx shadcn@latest add https://www.motionkitui.com/r/arrow-button.json in any project with a components.json. It saves the file to components/motionkit and installs its dependencies.
With reduced motion on, the arrows and label swap without sliding.
Magnetic Button
A button that leans toward the cursor with a springy, physical pull.
Shimmer Button
A dark button with a light sweep that draws the eye without shouting.
Ink Fill Button
An outline button that floods with colour from exactly where the cursor came in, and drains out towards where it leaves.