External Link
betaA link out of the platform that keeps the look of the text around it and names the destination's host before the user leaves.
@zenoxai/ui/patterns/external-link MarkdownThe vendor confirmed the flaw in version 7.4. See the vendor advisory for the affected builds and the fix.
<p className="max-w-md text-sm text-text-secondary">
The vendor confirmed the flaw in version 7.4. See the{" "}
<ExternalLink href="https://advisories.acme-security.example/ZX-2026-0142">
vendor advisory
</ExternalLink>{" "}
for the affected builds and the fix.
</p>Usage
import { ExternalLink } from "@zenoxai/ui/patterns/external-link"
<ExternalLink href={vendor.advisoryUrl}>Vendor advisory</ExternalLink>
Use it for every URL that leaves the platform. It inherits the font and the color of the text around it and keeps a thin underline, so a link in running text is told apart without its color.
- Only http and https. The href is parsed; anything else (a relative path,
mailto:, a defangedhxxps://,javascript:, a URL with credentials such ashttps://bank.example@evil.example) renders the children as plain text. - Always a new tab, with
noopener noreferrer: the investigation keeps its state, and the destination learns nothing about where the visit came from. - A disclosure on click. The popover names the destination’s host in monospace, in ASCII (an internationalized name shows its punycode beside the written form), so a lookalike shows before the user leaves. Copy link copies the URL; Open link opens it. Cmd, Ctrl, Shift and middle clicks go straight to a new tab: the user asked for one.
The disclosure is a speed bump, not a security control. Never use External Link on an indicator:
a phishing URL or a C2 host is data to copy, shown in font-mono with
Value Actions. For URLs inside a string, use
Linkified Text. For navigation inside the app, use the router’s link.
Write link text that names the destination when it is not the URL: “Vendor advisory”, not “Click here”.
Examples
A standalone value
In a detail field or a table cell, underline="hover" keeps the value plain until hovered.
- Registrar
- Acme Registrar, Inc.
- Registrar URL
- https://www.acme-registrar.example
- Abuse contact
- abuse@acme-registrar.example
<DescriptionList className="w-full max-w-md">
<DescriptionItem label="Registrar">Acme Registrar, Inc.</DescriptionItem>
<DescriptionItem label="Registrar URL">
<ExternalLink
href="https://www.acme-registrar.example"
underline="hover"
>
https://www.acme-registrar.example
</ExternalLink>
</DescriptionItem>
<DescriptionItem label="Abuse contact">
abuse@acme-registrar.example
</DescriptionItem>
</DescriptionList>A destination the app builds
confirm={false} skips the disclosure for URLs the app builds itself, such as an NVD page or the
vendor’s documentation. The scheme check, the new tab and rel stay. A URL that came from data
keeps the disclosure.
Scored 9.8 by CVE-2026-21490 on NVDOpens in a new tab..
<p className="max-w-md text-sm text-text-secondary">
Scored 9.8 by{" "}
<ExternalLink
href="https://nvd.nist.gov/vuln/detail/CVE-2026-21490"
confirm={false}
>
CVE-2026-21490 on NVD
</ExternalLink>
.
</p>Accessibility
- The link stays a link, with
aria-haspopup="dialog"andaria-expanded. When the disclosure opens, focus moves to the popup itself, so a screen reader reads the title and the host first and a repeated Enter cannot leave the platform. Tab then reaches Copy link, Open link and Close. - Escape and Close return focus to the link; a click elsewhere leaves focus where the user clicked.
- With
confirm={false}, the link’s description says “Opens in a new tab.”. - Clicks and Enter inside the link and the disclosure never reach a clickable row around it.
- Pass translated texts through
labels;descriptionreceives the host, already set.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
ExternalLink
ExternalLink is a link out of the platform that adds behavior and almost no look: it inherits the font and color of the text around it. It links only absolute http and https URLs, always opens a new tab with `noopener noreferrer`, and on click first shows a disclosure that names the destination's host, with Copy link and Open link.
| Prop | Type | Default |
|---|---|---|
confirm | booleanShows the disclosure before leaving. Pass `false` only for URLs the app builds itself (its docs, a vendor's site); URLs from data keep it. | true |
href* | string | null | undefinedThe destination. Anything but an absolute http or https URL renders as text. | - |
labels | Partial<ExternalLinkLabels>Already-translated texts. English by default. | - |
onCopyError | ((error: unknown) => void)Called when the URL could not be written to the clipboard. | - |
underline | "hover" | "always"`always` keeps a thin underline, for links in running text; `hover` underlines on hover only, for standalone values such as a table cell. | always |