Skip to content

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.

React AriaSource

By continuing you agree to our Terms of Service and Privacy Policy.

Installation

pnpm dlx shadcn@latest add @desyne/link

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Link } from "@/components/ui/link";
tsx
<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 buttonVariants to a React Aria or Next.js link.
  • Breadcrumbs and Pagination — structured navigation, built on the same link.

Anatomy

tsx
<Link href="…">   {/* <a>, or <span role="link"> without href */}
  <Icon />        {/* optional, size it yourself */}
  Label
  {/* ↗ arrow, added when isExternal */}
</Link>
PartRendersNotes
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.

Built on React Aria and styled with Tailwind CSS.

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.

Download invoiceView receipt

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.

app/providers.tsxtsx
"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:

app/providers.tsxtsx
declare module "react-aria-components" {
  interface RouterConfig {
    routerOptions: NonNullable<
      Parameters<ReturnType<typeof useRouter>["push"]>[1]
    >;
  }
}
tsx
<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

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.

Sign in
Welcome back. Enter your details to continue.

New to Acme? Create an account

By continuing you agree to the Terms and Privacy Policy.

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.
  • isDisabled sets aria-disabled and 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 an sr-only "(opens in a new tab)".
  • Don't rely on color alone to identify links in body text; use the underline variant there.

Keyboard

KeyAction
TabMoves focus to the link
EnterFollows the link

Styling

Data attributes

AttributePresent when
data-hoveredHovered with a mouse or pen
data-pressedBeing pressed
data-focused / data-focus-visibleFocused / focused with the keyboard
data-currentaria-current is set
data-disabledisDisabled is true
data-slot="link"Always

Render props

className and children accept a function of the link's state:

tsx
<Link href="/inbox" className={({ isCurrent }) => (isCurrent ? "font-semibold" : "")}>
  {({ isHovered }) => (isHovered ? "Open inbox →" : "Inbox")}
</Link>

linkVariants

The tailwind-variants function behind the component.

tsx
import { linkVariants } from "@/components/ui/link";

linkVariants({ variant: "subtle", className: "text-xs" });

API Reference

Prop

Type

Also accepts every prop of React Aria's Link.

linkVariants

Prop

Type

  • Button — for actions, and buttonVariants for button-styled links.
  • Breadcrumbs — a trail of links to parent pages.
  • Pagination — links between pages of results.
  • Sidebar — navigation links for an app.