Something new is coming.Join the waitlist

Animated Arrow

PreviousNext

A small arrow that slides through its container on parent hover. Drop inside any element with the group/animated-arrow class.

Installation

npx shadcn@latest add https://ui.srb.codes/r/animated-arrow.json

Usage

The arrow only animates when an ancestor element has the group/animated-arrow class — that's a Tailwind named group so the animation only fires for the surface you intend, not for any random .group element further up the tree (your card wrapper, a tabs panel, the docs preview shell). Drop the arrow inside a button, link, or any element you want to drive the animation from.

import Link from "next/link";
 
import { AnimatedArrow } from "@/components/animated-arrow";
 
export function ContinueLink() {
  return (
    <Link
      href="/docs"
      className="group/animated-arrow inline-flex items-center gap-2 rounded-full border px-4 py-2"
    >
      Read the docs
      <AnimatedArrow />
    </Link>
  );
}

Forget the group/animated-arrow class on the parent and the arrow will sit motionless — the component itself won't try to detect hover, by design, because most surfaces want the entire button to act as the hover target rather than just the arrow.

Patterns

Back arrow

Set backArrow to flip the direction. The slide animation flips with it — instead of the second arrow sliding in from the right, the first slides out to the left.

<Link href="/" className="group/animated-arrow inline-flex items-center gap-2">
  <AnimatedArrow backArrow />
  Back home
</Link>

Pill with a colored arrow

This is the marketing-pill pattern: a rounded badge whose arrow has its own background that intensifies on hover. The arrow's wrapper accepts a className, so you can stack background and color utilities directly.

<Link href="/changelog" className="group/animated-arrow inline-flex items-center gap-2 rounded-full border py-1 pr-1 pl-3">
  <span>What's new</span>
  <AnimatedArrow className="rounded-full bg-blue-500 text-white group-hover/animated-arrow:bg-blue-600" />
</Link>

Props

PropTypeDefaultDescription
backArrowbooleanfalseRender the arrow pointing left and reverse the slide direction so it animates out to the left on hover.
classNamestring—Forwarded to the outer size-6 wrapper. Add background, color, rounded, and group-hover/animated-arrow:* utilities to style it.

How the animation works

The component renders the icon twice inside a track that's twice the visible width. On parent hover, the track translates by 50% so the second icon slides into view while the first slides out — there's no layout shift on the consumer side because the outer wrapper stays a fixed size-6 with overflow-hidden. The duration is 500ms and the easing is ease-in-out, so it feels deliberate rather than twitchy.

Because the trigger is group-hover/animated-arrow, you can put the arrow anywhere inside the hover target — beside a label, after a span, between two icons — and it'll animate the moment the user enters the parent. There's no onMouseEnter listener and no client-side state, so the component renders fine in a React Server Component.

Why a named group, not plain group?

A bare group-hover: matches the nearest ancestor with the group class. That's fine in isolation, but the moment the arrow is dropped inside something that already uses group for its own hover styling — a card, a tabs panel, the documentation preview shell — the arrow starts firing whenever the cursor enters that outer surface, not the trigger you actually wanted. Tailwind's named-group syntax (group/<name> on the trigger, group-hover/<name>:* on the target) gives the component its own hover scope, so there's no collision with anything above it in the tree.