Link
A semantic anchor primitive for navigation, inline prose, and CTAs. Three variants, five sizes, five intents, three tones, configurable underline, external link handling, leading/trailing icons, and RTL support.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { Link } from "@tessinaui/ui";<Link href="/docs">Read the documentation</Link>
<Link variant="standalone" trailingIcon={<ArrowRight />} href="/pricing">
See pricing
</Link>
<Link external href="https://tessinaui.com">Tessina UI</Link>Showcase
Variants
| Variant | Default underline | When to use |
|---|---|---|
inline | always | Anchors inside a sentence or paragraph — the default |
standalone | hover | Navigation links, CTAs sitting outside prose |
quiet | none | Subtle links that inherit the surrounding colour |
Each variant sets a sensible underline default. You can override it explicitly with underline="always" \| "hover" \| "none" at any time.
Sizes
Mirrors Button and Field sizing so links line up with adjacent UI.
| Size | Text class | Icon size | Use case |
|---|---|---|---|
xs | text-xs | 14px | Dense UI, filter rails |
sm | text-sm | 16px | Compact layouts |
md | text-base | 16px | Default — body copy |
lg | text-lg | 20px | Large CTAs, settings |
xl | text-xl | 24px | Hero links, marketing pages |
Intents
Use intent to communicate meaning via colour. Any non-none intent overrides the tone choice for the default/muted tones; on-color intents stay tinted for dark surfaces.
| Intent | Token | Typical use |
|---|---|---|
none | text-primary | Default brand link |
error | text-error | Destructive or error context |
warning | text-warning | Caution |
success | text-success | Confirmation |
info | text-info | Informational |
Tones
| Tone | Default colour | Use case |
|---|---|---|
default | text-primary (brand) | Standard on light/background surfaces |
muted | text-muted-foreground | De-emphasised links (e.g. footers) |
on-color | text-background | Dark surfaces, inverted themes |
Underline
underline is orthogonal to variant — each variant has a sensible default, but you can pin the behaviour explicitly.
<Link underline="always">Always underlined</Link>
<Link underline="hover">Underline on hover</Link>
<Link underline="none">Never underlined</Link>External links
Pass external to flag a link as external. Tessina Link then:
- sets
target="_blank"(unless you pass an explicittarget) - sets
rel="noopener noreferrer"(unless you pass an explicitrel) - appends an
ExternalLinktrailing icon
Turn the icon off with showExternalIcon={false}.
<Link external href="https://tessinaui.com">
Tessina UI
</Link>
<Link external showExternalIcon={false} href="https://tessinaui.com">
Tessina UI
</Link>Icons
<Link variant="standalone" leadingIcon={<ArrowLeft />} href="/projects">
Back to projects
</Link>
<Link variant="standalone" trailingIcon={<ArrowRight />} href="/pricing">
See pricing
</Link>Icons are wrapped in an aria-hidden span and sized by size. A custom trailingIcon overrides the auto external icon.
Disabled
<Link disabled href="/billing">
Billing portal
</Link>disabled visually mutes the link, sets aria-disabled="true", removes it from the tab order via tabIndex={-1}, and strips the href so it can't be navigated.
Integrating with Next.js / React Router
Use the render prop to inherit Tessina Link styling while preserving your router's navigation semantics.
import NextLink from "next/link";
<Link render={<NextLink href="/dashboard" />}>Dashboard</Link>import { NavLink } from "react-router-dom";
<Link render={<NavLink to="/dashboard" />}>Dashboard</Link>RTL
Pass dir="rtl" to flip icon order and spacing via logical-flow properties.
<Link dir="rtl" variant="standalone" leadingIcon={<ArrowLeft />} href="#">
عودة إلى المشاريع
</Link>Accessibility
- Renders a native
<a>element with the correct role, focus ring, and keyboard behaviour. - External links get
rel="noopener noreferrer"automatically. - Disabled links set
aria-disabled="true"and are removed from the tab order. - Keep link labels descriptive — "Read the full post" beats "click here".
- When a link looks like a button, consider using
Buttonwithrender={<a href="..." />}instead — buttons and links have different semantics.
API Reference
Link
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "inline" | "standalone" | "quiet" | "inline" | Style personality. Drives the default underline and weight. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Size scale |
intent | "none" | "error" | "warning" | "success" | "info" | "none" | Validation / meaning colour |
tone | "default" | "muted" | "on-color" | "default" | Base colour tone |
weight | "normal" | "medium" | "semibold" | variant-based | Font weight. Inline links default to normal; others to medium. |
underline | "always" | "hover" | "none" | variant-based | Underline behaviour. Overrides the variant default. |
external | boolean | false | Opens in a new tab with rel="noopener noreferrer" and shows an external icon. |
showExternalIcon | boolean | true | Hide the auto-added external icon when external is true. |
leadingIcon | ReactNode | — | Icon rendered before the label |
trailingIcon | ReactNode | — | Icon rendered after the label. Overrides the auto external icon when provided. |
disabled | boolean | false | Visually mute the link, set aria-disabled, remove href, and drop from tab order |
render | ReactElement | — | Wraps another element (e.g. <NextLink>) to inherit Link styling |
dir | "ltr" | "rtl" | — | Text direction |
href | string | — | Native href attribute |
target | string | — | Native target. Falls back to _blank when external is true. |
rel | string | — | Native rel. Falls back to noopener noreferrer when external is true and target="_blank". |
className | string | — | Extra classes on the root <a> element |
The component extends all standard <a> HTML attributes.