Arrow Slide Button
Original · freeButtons where an arrow slides in and the label shifts on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/btn-arrow-slide.json"use client";
import { useState, type ReactNode } from "react";
type Size = "sm" | "md" | "lg";
const sizeMap: Record<Size, string> = {
sm: "h-9 px-4 text-sm gap-2 rounded-lg",
md: "h-11 px-5 text-[0.9375rem] gap-2.5 rounded-xl",
lg: "h-14 px-7 text-base gap-3 rounded-2xl",
};
const arrowSizeMap: Record<Size, string> = {
sm: "h-4 w-4",
md: "h-[1.125rem] w-[1.125rem]",
lg: "h-5 w-5",
};
function ArrowGlyph({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.25}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M5 12h14" />
<path d="m13 6 6 6-6 6" />
</svg>
);
}
function Spinner({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
className={`bas-spin ${className ?? ""}`}
aria-hidden="true"
>
<circle
cx="12"
cy="12"
r="9"
stroke="currentColor"
strokeWidth={2.5}
className="opacity-25"
/>
<path
d="M21 12a9 9 0 0 0-9-9"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
/>
</svg>
);
}
const focusRing =
"outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950";
/* ── Variant 1: arrow slides in from the right edge, label shifts left ── */
function SlideInRight({ size = "md" }: { size?: Size }) {
return (
<button
type="button"
className={`bas-group group relative inline-flex items-center justify-center overflow-hidden font-semibold text-white shadow-sm transition-all duration-300 active:scale-[0.98] bg-indigo-600 hover:bg-indigo-500 focus-visible:ring-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400 ${sizeMap[size]} ${focusRing}`}
>
<span className="bas-shift inline-block transition-transform duration-300 ease-out group-hover:-translate-x-1.5">
Get started
</span>
<span className="bas-enter-r pointer-events-none absolute right-4 flex translate-x-6 items-center opacity-0 transition-all duration-300 ease-out group-hover:translate-x-0 group-hover:opacity-100">
<ArrowGlyph className={arrowSizeMap[size]} />
</span>
</button>
);
}
/* ── Variant 2: arrow slides in from the left, label shifts right ── */
function SlideInLeft({ size = "md" }: { size?: Size }) {
return (
<button
type="button"
className={`bas-group group relative inline-flex items-center justify-center overflow-hidden border font-semibold transition-all duration-300 active:scale-[0.98] border-zinc-300 bg-white text-zinc-900 hover:border-zinc-400 focus-visible:ring-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:hover:border-zinc-500 ${sizeMap[size]} ${focusRing}`}
>
<span className="bas-enter-l pointer-events-none absolute left-4 flex -translate-x-6 items-center opacity-0 transition-all duration-300 ease-out group-hover:translate-x-0 group-hover:opacity-100">
<ArrowGlyph className={arrowSizeMap[size]} />
</span>
<span className="bas-shift inline-block transition-transform duration-300 ease-out group-hover:translate-x-1.5">
View pricing
</span>
</button>
);
}
/* ── Variant 3: swap — resting arrow exits right, fresh arrow enters left ── */
function ArrowSwap({ size = "md" }: { size?: Size }) {
return (
<button
type="button"
className={`bas-group group relative inline-flex items-center justify-center font-semibold text-white shadow-sm transition-all duration-300 active:scale-[0.98] bg-emerald-600 hover:bg-emerald-500 focus-visible:ring-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 ${sizeMap[size]} ${focusRing}`}
>
<span>Continue</span>
<span className="relative flex h-full items-center overflow-hidden" style={{ width: "1.25rem" }}>
<ArrowGlyph
className={`${arrowSizeMap[size]} absolute transition-all duration-300 ease-out group-hover:translate-x-6 group-hover:opacity-0`}
/>
<ArrowGlyph
className={`${arrowSizeMap[size]} absolute -translate-x-6 opacity-0 transition-all duration-300 ease-out group-hover:translate-x-0 group-hover:opacity-100`}
/>
</span>
</button>
);
}
/* ── Variant 4: underline link with a nudging arrow ── */
function LinkArrow({ size = "md" }: { size?: Size }) {
return (
<button
type="button"
className={`bas-group group inline-flex items-center gap-1.5 rounded-md font-semibold text-violet-700 transition-colors hover:text-violet-600 focus-visible:ring-violet-500 dark:text-violet-300 dark:hover:text-violet-200 ${
size === "sm" ? "text-sm" : size === "lg" ? "text-base" : "text-[0.9375rem]"
} px-1 py-0.5 ${focusRing}`}
>
<span className="bg-gradient-to-r from-current to-current bg-[length:0%_1.5px] bg-left-bottom bg-no-repeat transition-[background-size] duration-300 ease-out group-hover:bg-[length:100%_1.5px]">
Read the case study
</span>
<ArrowGlyph
className={`${arrowSizeMap[size]} transition-transform duration-300 ease-out group-hover:translate-x-1`}
/>
</button>
);
}
/* ── Variant 5: pill with an arrow reveal in a circular chip ── */
function ChipArrow({ size = "md" }: { size?: Size }) {
return (
<button
type="button"
className={`bas-group group inline-flex items-center gap-3 rounded-full border border-amber-300 bg-amber-50 pr-1.5 font-semibold text-amber-900 transition-colors hover:border-amber-400 hover:bg-amber-100 focus-visible:ring-amber-500 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200 dark:hover:bg-amber-500/20 ${
size === "sm" ? "h-9 pl-4 text-sm" : size === "lg" ? "h-14 pl-7 text-base" : "h-11 pl-5 text-[0.9375rem]"
} ${focusRing}`}
>
<span>Book a demo</span>
<span
className={`flex items-center justify-center rounded-full bg-amber-500 text-amber-950 transition-transform duration-300 ease-out group-hover:translate-x-0.5 dark:bg-amber-400 ${
size === "sm" ? "h-6 w-6" : size === "lg" ? "h-10 w-10" : "h-8 w-8"
}`}
>
<ArrowGlyph className={arrowSizeMap[size]} />
</span>
</button>
);
}
/* ── Variant 6: async submit — idle → loading → done, arrow-driven ── */
function AsyncArrow({ size = "md" }: { size?: Size }) {
const [status, setStatus] = useState<"idle" | "loading" | "done">("idle");
function handleClick() {
if (status !== "idle") return;
setStatus("loading");
window.setTimeout(() => setStatus("done"), 1400);
window.setTimeout(() => setStatus("idle"), 3400);
}
return (
<button
type="button"
onClick={handleClick}
disabled={status === "loading"}
aria-live="polite"
className={`bas-group group relative inline-flex min-w-[11rem] items-center justify-center overflow-hidden font-semibold text-white shadow-sm transition-all duration-300 active:scale-[0.98] disabled:cursor-wait bg-rose-600 hover:bg-rose-500 focus-visible:ring-rose-500 dark:bg-rose-500 dark:hover:bg-rose-400 ${sizeMap[size]} ${focusRing}`}
>
{status === "idle" && (
<>
<span className="bas-shift inline-block transition-transform duration-300 ease-out group-hover:-translate-x-1.5">
Deploy to production
</span>
<span className="pointer-events-none absolute right-4 flex translate-x-6 items-center opacity-0 transition-all duration-300 ease-out group-hover:translate-x-0 group-hover:opacity-100">
<ArrowGlyph className={arrowSizeMap[size]} />
</span>
</>
)}
{status === "loading" && (
<span className="inline-flex items-center gap-2.5">
<Spinner className={arrowSizeMap[size]} />
Deploying…
</span>
)}
{status === "done" && (
<span className="inline-flex items-center gap-2.5">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
strokeLinejoin="round"
className={arrowSizeMap[size]}
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
Deployed
</span>
)}
</button>
);
}
/* ── Variant 7: toggle follow — aria-pressed, arrow ↔ check ── */
function ToggleArrow({ size = "md" }: { size?: Size }) {
const [on, setOn] = useState(false);
return (
<button
type="button"
aria-pressed={on}
onClick={() => setOn((v) => !v)}
className={`bas-group group inline-flex items-center gap-2 font-semibold transition-all duration-300 active:scale-[0.98] ${sizeMap[size]} ${focusRing} ${
on
? "border border-sky-500 bg-sky-500 text-white hover:bg-sky-600 focus-visible:ring-sky-500 dark:border-sky-400 dark:bg-sky-500 dark:hover:bg-sky-400"
: "border border-sky-300 bg-transparent text-sky-700 hover:bg-sky-50 focus-visible:ring-sky-500 dark:border-sky-500/50 dark:text-sky-300 dark:hover:bg-sky-500/10"
}`}
>
{on ? (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
strokeLinejoin="round"
className={arrowSizeMap[size]}
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
) : (
<ArrowGlyph
className={`${arrowSizeMap[size]} transition-transform duration-300 ease-out group-hover:translate-x-1`}
/>
)}
{on ? "Subscribed" : "Subscribe"}
</button>
);
}
function Row({ label, children }: { label: string; children: ReactNode }) {
return (
<div className="flex flex-col gap-4">
<span className="text-xs font-medium uppercase tracking-widest text-zinc-400 dark:text-zinc-500">
{label}
</span>
<div className="flex flex-wrap items-center gap-4">{children}</div>
</div>
);
}
export default function BtnArrowSlide() {
return (
<section className="relative w-full bg-white px-6 py-20 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 sm:px-10 sm:py-28">
<style>{`
@keyframes bas-spin { to { transform: rotate(360deg); } }
.bas-spin { animation: bas-spin 0.7s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.bas-group, .bas-group * { transition-duration: 0.001ms !important; }
.bas-shift, .bas-enter-r, .bas-enter-l { transition: none !important; transform: none !important; }
.bas-spin { animation-duration: 1.6s; }
}
`}</style>
<div className="mx-auto flex max-w-3xl flex-col gap-14">
<header className="flex flex-col gap-3">
<h2 className="text-2xl font-bold tracking-tight sm:text-3xl">
Arrow-slide buttons
</h2>
<p className="max-w-prose text-[0.9375rem] leading-relaxed text-zinc-500 dark:text-zinc-400">
Hover, focus, or press each button. The arrow slides in from an edge
while the label shifts to make room — every one is a real{" "}
<code className="rounded bg-zinc-100 px-1 py-0.5 text-[0.8125rem] text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300">
<button>
</code>{" "}
with a focus-visible ring.
</p>
</header>
<Row label="Slide in — right">
<SlideInRight size="sm" />
<SlideInRight size="md" />
<SlideInRight size="lg" />
</Row>
<Row label="Slide in — left · outline">
<SlideInLeft size="sm" />
<SlideInLeft size="md" />
<SlideInLeft size="lg" />
</Row>
<Row label="Arrow swap">
<ArrowSwap size="sm" />
<ArrowSwap size="md" />
<ArrowSwap size="lg" />
</Row>
<Row label="Inline link">
<LinkArrow size="sm" />
<LinkArrow size="md" />
<LinkArrow size="lg" />
</Row>
<Row label="Chip reveal">
<ChipArrow size="sm" />
<ChipArrow size="md" />
<ChipArrow size="lg" />
</Row>
<Row label="Async submit — click me">
<AsyncArrow size="md" />
<AsyncArrow size="lg" />
</Row>
<Row label="Toggle — aria-pressed">
<ToggleArrow size="sm" />
<ToggleArrow size="md" />
<ToggleArrow size="lg" />
</Row>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Shimmer Button
MITA dark pill button with a continuous conic-gradient light shimmer sweeping around its border.

Rainbow Gradient Button
MITA button wrapped in an animated multi-colour gradient border with a matching blurred underglow.

Interactive Hover Button
MITA pill button whose dot expands to fill the surface and reveals an arrow label on hover.

Pulsating Button
MITA solid button that emits a soft, continuously pulsing glow ring to draw the eye to the primary action.

Shiny Button
MITA frosted-glass button with a spring-animated light sweeping across its text and border.

Solid Set Button
OriginalA set of solid buttons: primary, secondary, success and danger, in three sizes.

Outline Set Button
OriginalOutline button variants across colours and sizes with a hover fill.

Ghost Set Button
OriginalGhost and text buttons with subtle hover backgrounds.

Soft Set Button
OriginalSoft, tinted buttons across semantic colours.

Gradient Set Button
OriginalGradient-filled buttons with a hover shift.
Icon Leading Button
OriginalButtons with leading and trailing icons.
Icon Only Button
OriginalSquare and circular icon-only buttons with tooltips and aria-labels.

