Saltar al contenido
ssntl/ui

Link

Un enlace en línea, subrayado además de azul, que dice a dónde lleva y parte las URL largas.

Orders over Q500 ship free. See the shipping rates for everything else.

Read the ssntl/ui documentation (opens in a new tab) before you install.

Tracking: https://ssntl.app/orders/2026-10-28/GT-48210/tracking?ref=confirmation-email

Instalación

$ pnpm dlx shadcn@latest add @ssntl/link

Uso

tsx
import { Link } from "@/components/ui/link"

<p>Consulta las <Link href="/envios">tarifas de envío</Link> para lo demás.</p>
<Link href="https://ui.ssntl.app/docs" external>documentación de ssntl/ui</Link>
<Link asChild><NextLink href="/precios">Precios</NextLink></Link>
  • Evidente: azul, peso medio y subrayado, así un enlace nunca depende solo del color.
  • Descriptivo: el texto nombra el destino (“tarifas de envío”), nunca “haz clic aquí” ni “leer más”.
  • Las URL largas se parten donde hace falta en lugar de ensanchar el diseño.
  • external abre una pestaña nueva, agrega la flecha, rel="noopener noreferrer" y “(se abre en una pestaña nueva)” para lectores de pantalla (newTabLabel).

Código

components/ui/link.tsx
import * as React from "react"
import { ArrowUpRight } from "lucide-react"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

// Essential Design links: blue-600 (blue-300 in dark), medium weight and
// always underlined, so a link never depends on color alone. The underline
// starts soft and fills in on hover. Long URLs break instead of overflowing.
function Link({
  asChild = false,
  external = false,
  newTabLabel = "(opens in a new tab)",
  className,
  children,
  ...props
}: React.ComponentProps<"a"> & {
  /** Render the child element (a framework Link, for example) with these styles. */
  asChild?: boolean
  /** Opens in a new tab: adds the arrow, rel and a label for screen readers. */
  external?: boolean
  /** Read after the text of an external link. */
  newTabLabel?: string
}) {
  const Comp = asChild ? Slot.Root : "a"

  return (
    <Comp
      data-slot="link"
      {...(external && { target: "_blank", rel: "noopener noreferrer" })}
      className={cn(
        "rounded-xs font-medium wrap-break-word text-blue-600 underline decoration-blue-600/40 underline-offset-4 dark:text-blue-300 dark:decoration-blue-300/40",
        "transition-[text-decoration-color] duration-(--duration-fast) ease-out hover:decoration-current",
        "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
        className
      )}
      {...props}
    >
      <Slot.Slottable>{children}</Slot.Slottable>
      {external && (
        <>
          <ArrowUpRight
            aria-hidden
            strokeWidth={1.6}
            className="ms-0.5 inline size-4 align-text-bottom"
          />
          <span className="sr-only"> {newTabLabel}</span>
        </>
      )}
    </Comp>
  )
}

export { Link }