Link

The text link, in both house treatments. default and muted are the standalone UI link for chrome, warming to the brand accent on hover; prose is the hyperlink embedded in running copy, underlined at rest.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add link

Usage#

usage.tsxTSX
import { Link } from "@/components/ui/link"export function Example() {  return <Link href="/pricing">See pricing</Link>}

Variants#

default rests at text-foreground (full-strength, reads as a real label); muted starts quiet for a value in a directory or a footer link. Both lift to the brand accent on hover. prose is the third treatment, for a link inside a sentence: it sets no rest color, so it inherits the copy around it, and only its underline marks it until hover turns both text and underline the accent.

Full-strength linkMuted link

Muted copy with a prose link that inherits it.

Full-strength copy with a prose link that inherits it.

In prose#

A hyperlink inside running copy is a different animal from a link in UI chrome, so prose is a treatment rather than a color: it inherits the paragraph's color, wears a hairline decoration-border underline at rest, and turns both the text and the underline the --link token on hover. It stays display: inline so it breaks across lines mid-sentence instead of wrapping as an unbreakable block, drops the trailing-icon reveal the standalone variants carry (a glyph in prose is content, not a hover affordance), and hovers on the faster 160ms beat, because a word-sized target in a paragraph is swept over constantly and a slow fade out leaves a trail of words cooling back down.

in-prose.tsxTSX
import NextLink from "next/link"import { Link } from "@/components/ui/link"export function Example() {  return (    <p className="text-muted-foreground">      Components are owned source in your repo. Start with the{" "}      {/* asChild keeps an internal prose link on client-side routing. */}      <Link asChild variant="prose">        <NextLink href="/docs/installation">installation guide</NextLink>      </Link>      .    </p>  )}

Underline#

The color shift carries the affordance in context, so the link is not underlined by default. Pass underline for a prose-like link that must read as underlined at rest. It applies to default and muted only: prose is underlined by definition, and pinning a thickness on it (the prop sets decoration-1) would stop the underline tracking the font metrics, so combining the two is a type error.

With an icon#

Drop a trailing glyph (a directional arrow for an outbound or routing link) beside the label. It reads as a hover affordance, so it stays hidden at rest and slides and fades in when the link is hovered or keyboard-focused, warming to brand with the text. The motion is a plain CSS transition (interruptible), and it collapses to an instant reveal under prefers-reduced-motion.

As child#

Use asChild to render the link styles onto another element, e.g. Next.js's Link, via Radix Slot, so you keep client-side routing.

as-child.tsxTSX
import NextLink from "next/link"import { Link } from "@/components/ui/link"export function Example() {  return (    <Link asChild>      <NextLink href="/pricing">See pricing</NextLink>    </Link>  )}

FAQ#

Ask where the link sits. Outside running prose (a contact row, a directory card, a footer column, a meta row) use default, or muted when it should start quiet: both rest at a solid color and warm to brand on hover. Inside a sentence, use prose. Never hand-roll the classes: this component is the only place the treatment is defined.