ComponentsNavigation
Link
Navigate to another page or resource. Three styles, an external mode that opens a new tab with an arrow, disabled and current states, and client-side routing through React Aria's RouterProvider.
Installation
pnpm dlx shadcn@latest add @desyne/linkThe CLI installs dependencies and any other components this one uses.
Usage
import { Link } from "@/components/ui/link";<Link href="/pricing">Pricing</Link>When to use
- Link — navigation: to another page, a section of this page, a file download or an email address.
- Button — an action that doesn't navigate (submit, open a dialog, delete). If it needs to look like a link, use
<Button variant="link">. - A button-styled link — navigation that should look like a button. Apply
buttonVariantsto a React Aria or Next.js link. - Breadcrumbs and Pagination — structured navigation, built on the same link.
Anatomy
<Link href="…"> {/* <a>, or <span role="link"> without href */}
<Icon /> {/* optional, size it yourself */}
Label
{/* ↗ arrow, added when isExternal */}
</Link>| Part | Renders | Notes |
|---|---|---|
Link | <a> or <span role="link"> | An <a> when href is set and the link isn't disabled; otherwise a focusable <span> with role="link". Inline flex with a small gap. |
| External arrow | <svg> | Appended after the children when isExternal. aria-hidden. |
Examples
Variants
default is the brand-colored, medium-weight link for calls to action and standalone links. subtle is muted until hovered, for footers, metadata and dense navigation. underline keeps the text color with a soft underline, for links inside body copy where color alone isn't enough.
External
isExternal sets target="_blank" and rel="noreferrer" and adds an arrow icon. Pass your own target or rel to override them.
Disabled
isDisabled renders a <span> instead of an <a>, removes it from the tab order and dims it. Prefer hiding links that can never be followed; disable ones that will become available.
With icon
Children are laid out in an inline flex row, so icons line up with the text. Icons aren't sized automatically; add size-4 (or size-3.5 for small text) and widen the gap with gap-1.5. download and mailto: links work as on a native <a>.
Current page
Set aria-current="page" on the link for the page you're on. React Aria exposes it as data-current, so you can style it with data-current:….
With a router
Wrap your app in React Aria's RouterProvider and every Link (plus breadcrumbs, tabs, menu items and other link-capable components) navigates with your client-side router. Modifier clicks, target="_blank" and cross-origin links still use the browser.
"use client";
import { useRouter } from "next/navigation";
import { RouterProvider } from "react-aria-components";
export function Providers({ children }: { children: React.ReactNode }) {
const router = useRouter();
return <RouterProvider navigate={router.push}>{children}</RouterProvider>;
}Pass router-specific options with routerOptions; they're forwarded as the second argument to navigate. Register their type once so they're type-checked:
declare module "react-aria-components" {
interface RouterConfig {
routerOptions: NonNullable<
Parameters<ReturnType<typeof useRouter>["push"]>[1]
>;
}
}<Link href="/settings" routerOptions={{ scroll: false }}>
Settings
</Link>To use Next.js prefetching, style next/link with linkVariants instead. Hover and focus styles in linkVariants rely on React Aria's data-hovered and data-focus-visible attributes, so add hover: and focus-visible: classes when using it on a plain element.
Recipes
Site footer
Columns of subtle links grouped in labelled <nav> elements, with legal links and an external link in the bottom row.
Sign-in form
Inline links around a form: "Forgot password?" aligned under the field, a sign-up prompt, and underline links in the legal text.
Accessibility
- With
href, renders a native<a>, so browser features like "open in new tab", copy link address and the status-bar preview work, and screen readers list it as a link. - Without
href(or when disabled), renders a<span role="link">that's focusable and responds to Enter. Prefer a Button for actions. isDisabledsetsaria-disabledand removes the link from the tab order.- The focus ring appears only for keyboard focus (
data-focus-visible). - The external arrow is
aria-hidden. If opening a new tab could surprise users, say so in the text or add ansr-only"(opens in a new tab)". - Don't rely on color alone to identify links in body text; use the
underlinevariant there.
Keyboard
| Key | Action |
|---|---|
| Tab | Moves focus to the link |
| Enter | Follows the link |
Styling
Data attributes
| Attribute | Present when |
|---|---|
data-hovered | Hovered with a mouse or pen |
data-pressed | Being pressed |
data-focused / data-focus-visible | Focused / focused with the keyboard |
data-current | aria-current is set |
data-disabled | isDisabled is true |
data-slot="link" | Always |
Render props
className and children accept a function of the link's state:
<Link href="/inbox" className={({ isCurrent }) => (isCurrent ? "font-semibold" : "")}>
{({ isHovered }) => (isHovered ? "Open inbox →" : "Inbox")}
</Link>linkVariants
The tailwind-variants function behind the component.
import { linkVariants } from "@/components/ui/link";
linkVariants({ variant: "subtle", className: "text-xs" });API Reference
Link
Prop
Type
Also accepts every prop of React Aria's Link.
linkVariants
Prop
Type
Related
- Button — for actions, and
buttonVariantsfor button-styled links. - Breadcrumbs — a trail of links to parent pages.
- Pagination — links between pages of results.
- Sidebar — navigation links for an app.