Skip to content
Caseroom

Components · Actions

Link

Inline in text, standalone with an arrow, or external with an arrow out and a new-tab note for screen readers.

3.7% of the 2,062 sites in The UI Index have a link (77 sites).

Deploys run on every push. Read how previews work before you invite your team.

See all integrationsStatus page (opens in a new tab)

Both previews render the same markup; the HTML tab below is that markup without a framework.

Install

React
npx shadcn@latest add https://www.themes.dev/caseroom/r/link.json
Astro
npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/link.json

Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.

link.tsx
import type { AnchorHTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
import { ARROW_PATH, EXTERNAL_PATH, linkVariants, type LinkVariants } from './link.variants';

export type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & LinkVariants & { href: string; newTab?: boolean };

/** A link: inline in text, standalone with an arrow, or external with an arrow out. */
export function Link({ variant, href, newTab, className, children, ...props }: LinkProps) {
  const icon = variant === 'standalone' ? ARROW_PATH : variant === 'external' ? EXTERNAL_PATH : null;
  return (
    <a href={href} target={newTab ? '_blank' : undefined} rel={newTab ? 'noopener' : undefined} className={cn(linkVariants({ variant }), className)} {...props}>
      {children}
      {icon && <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={icon} /></svg>}
      {newTab && <span className="sr-only"> (opens in a new tab)</span>}
    </a>
  );
}
export { linkVariants };
link.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';

/** Links. inline: in running text, underlined (the underline darkens on hover and thickens when pressed).
 *  standalone: on its own line, with an arrow that steps forward on hover. external: opens elsewhere, marked with
 *  an arrow out and "(opens in a new tab)" for screen readers when it does. */
export const linkVariants = cva(
  'rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform',
  {
    variants: {
      variant: {
        inline: 'underline decoration-foreground/30 hover:decoration-foreground active:decoration-2',
        standalone: 'group inline-flex min-h-6 items-center gap-1.5 hover:underline active:decoration-2 hover:[&_svg]:translate-x-0.5',
        external: 'inline-flex min-h-6 items-center gap-1 underline decoration-foreground/30 hover:decoration-foreground active:decoration-2 hover:[&_svg]:-translate-y-px hover:[&_svg]:translate-x-px',
      },
    },
    defaultVariants: { variant: 'inline' },
  },
);
export type LinkVariants = VariantProps<typeof linkVariants>;
export const ARROW_PATH = 'M3 8h10M9 4l4 4-4 4';
export const EXTERNAL_PATH = 'M5 11l6-6M6 5h5v5';