Arrow link
A plain text link with a small arrow that nudges right on hover and focus.
npx shadcn@latest add https://ui.goodtabs.co/r/arrow-link.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/arrow-link
External
Opens in a new tab, with rel set and a note for screen readers.
In a sentence
Every component reads shadcn theme tokens only, so it matches your project the moment it lands. Read the docs
The sm size at the end of a grey paragraph.
Usage
import { ArrowLink } from "@/components/ui/arrow-link"
<ArrowLink href="/docs">Learn more</ArrowLink>
<ArrowLink href="https://github.com/acme" external>View on GitHub</ArrowLink>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | UrlObject | Where the link goes. Passed to next/link. | |
| external | boolean | false | Opens in a new tab with rel="noopener noreferrer" and a sr-only note. |
| size | "sm" | "default" | "default" | Text size: 12px with a 12px arrow, or 14px with a 14px arrow. |
| children | ReactNode | The link text. Keep it short, two to four words. |
Source
arrow-link/arrow-link.tsx
import * as React from "react";
import Link from "next/link";
import { ArrowRightIcon } from "lucide-react";
import { cn } from "@/lib/utils";
type ArrowLinkProps = React.ComponentProps<typeof Link> & {
/** Open in a new tab with rel="noopener noreferrer" and a screen reader note. */
external?: boolean;
/** Text size. sm is 12px, default is 14px. */
size?: "sm" | "default";
};
/**
* A plain text link with a small arrow that nudges right on hover and focus.
* Monochrome: the arrow is the hover signal, not a colour change.
*/
export function ArrowLink({ external = false, size = "default", className, children, ...props }: ArrowLinkProps) {
return (
<Link
data-slot="arrow-link"
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
className={cn(
"group/arrow-link inline-flex items-center gap-1 rounded-sm font-medium text-foreground transition-colors duration-100 ease-out outline-none hover:text-foreground/85 focus-visible:ring-2 focus-visible:ring-ring/50",
size === "sm" ? "text-xs" : "text-sm",
className,
)}
{...props}
>
<span>{children}</span>
<ArrowRightIcon
aria-hidden="true"
className={cn(
"shrink-0 transition-transform duration-150 ease-out group-hover/arrow-link:translate-x-0.5 group-focus-visible/arrow-link:translate-x-0.5 motion-reduce:transition-none motion-reduce:group-hover/arrow-link:translate-x-0 motion-reduce:group-focus-visible/arrow-link:translate-x-0",
size === "sm" ? "size-3" : "size-3.5",
)}
/>
{external && <span className="sr-only">(opens in a new tab)</span>}
</Link>
);
}
For AI agents
Hand your agent this page, the markdown at /components/arrow-link/prompt.md, or the Copy AI prompt button above.
When to use
- The quiet second action next to a primary button in a hero or section header
- A Learn more or See all link at the end of a feature, card or paragraph
- Anywhere a link should read as a link without an underline
When not to use
- As the only call to action on a page; use a solid Button for that
- Inside long prose where several links sit in one paragraph; use plain underlined links
- For actions that are not navigation, such as submit or delete
Agent notes
- Server component built on next/link, so it needs a Next.js app and a real href; it accepts every Link prop
- Monochrome by design: text stays foreground, the 2px arrow nudge is the hover and focus signal
- The nudge is off under prefers-reduced-motion; the arrow still shows, so the link reads the same
- external adds target, rel and a sr-only (opens in a new tab) note; the visible text does not change