Link
Router-agnostic textual link. By default it renders a native <a>;
for SPA navigation, pass as with the consumer framework's Link
(Next.js, react-router, Remix, etc.). External URLs (http(s), mailto:,
tel:, sms:) are detected automatically and get target="_blank" +
rel="noopener noreferrer" (merged with the consumer's rel), ignoring the
consumer's as — absolute URLs break when run through a router's basename,
so external links are always forced to a native <a> regardless of what
as was passed.
When to use
| ✅ Use when… | 🚫 Avoid when… |
|---|---|
|
|
Regular Link (native <a>)
Pass href to create a basic link — renders a native <a>.
loading
For external URLs, target="_blank" and rel="noopener noreferrer" are
added automatically.
rel is merged, not overwritten
noopener and noreferrer are non-negotiable on external links and always
come first, but the consumer's rel is preserved — useful for SEO hygiene on
external source citation links (nofollow), user-generated content (ugc),
paid links (sponsored) or identity (me). Tokens are deduplicated and
normalized to lowercase.
<Link href="https://example.com" rel="nofollow" text="Article citation" />
// renders: rel="noopener noreferrer nofollow"target, on the other hand, remains forced on external links — new tab is
the DS contract. On internal href the consumer's rel is passed through
as-is, with no injection.
Content: text or children
Pass the label as text (a string) or as children (JSX) — children
takes precedence. Using children makes Link the sanctioned replacement for a
hand-rolled <a> (or a raw factory anchor): you render arbitrary content while
keeping the DS link's external-link security and its guaranteed AA contrast
floor. Reach for Link — not Text as="a" — for anything that navigates;
Text as="a" is a plain anchor and gets none of that.
loading
// string
<Link href="/docs" text="Docs" />
// JSX children — takes precedence over text
<Link href="/docs"><strong>Docs</strong> — read the guide</Link>Link is a content/typography element — it does not lay its own children out
with flex. When a link's content needs layout (an icon beside a wordmark), nest
a layout container as the child; the anchor still keeps the link security +
contrast:
<Link href="/" contrast="main" textDecoration="none">
<Flex as="span" flexDirection="row" alignItems="center" gap="xs">
<Icon icon={logo} />
<Text as="span">Home</Text>
</Flex>
</Link>Integrating with a routing framework
For SPA navigation, pass the framework's Link via the as prop. Extra
props (to, href, prefetch, etc.) are forwarded via spread — the DS
does not dictate the router's contract.
Next.js
import NextLink from 'next/link';
import { Link } from '@apollion-dsi/core/elements/link';
<Link as={NextLink} href="/perfil" text="My profile" />;react-router-dom
import { Link as RouterLink } from 'react-router-dom';
import { Link } from '@apollion-dsi/core/elements/link';
<Link as={RouterLink} to="/perfil" text="My profile" />;Remix
import { Link as RemixLink } from '@remix-run/react';
import { Link } from '@apollion-dsi/core/elements/link';
<Link as={RemixLink} to="/perfil" text="My profile" />;Link as a Button
Use Link.Button for a link that looks like a Button
— it inherits variants, colors and sizes. Same polymorphic pattern via as.
<Link.Button
href="https://pt-br.react.dev/blog/2023/03/16/introducing-react-dev"
text="React Link as a Button"
variant="contained"
color="warning"
/>;
// With a framework:
import NextLink from 'next/link';
<Link.Button as={NextLink} href="/login" text="Sign in" variant="contained" />;Linked variant as inline navigation
variant="linked" is the chrome-less Link.Button: no chip padding, no
forced bold — it shares the text's left edge, so a breadcrumb or quiet nav
can use it without switching to a plain Link.
<Link.Button href="/" text="Home" variant="linked" size="small" />Underline and accessibility
The link underline follows the rule of never distinguishing a link by color
alone (WCAG 1.4.1). There are two modes, and a context engine in Text.
Default: permanent underline
A standalone Link comes underlined — the safe mode for inline links in
running text.
Quiet link (textDecoration="none")
In chrome/navigation (navbar, footer) the permanent underline feels heavy. Pass
textDecoration="none" to remove it at rest; :hover and :focus-visible
restore the underline, keeping the accessibility affordance without the link
looking like a button.
<Link href="/docs" textDecoration="none" text="Docs" />
// no underline at rest; underline on mouse hover / keyboard focusLink inside prose (context engine)
Any Link inside a <Text> is underlined by the container,
regardless of the link's own textDecoration — the one who knows it is
prose is the Text, so it is the one asserting the rule. Link.Button is
left out and keeps its button appearance.
Read our privacy policy and the terms of use before proceeding.
Sign in<Text variant="p">
Read our <Link href="/privacidade" text="privacy policy" /> and the{' '}
<Link href="/termos" textDecoration="none" text="terms of use" /> before proceeding.{' '}
<Link.Button href="/entrar" text="Sign in" variant="contained" />
</Text>
// both text links get underlined (even the textDecoration="none" one);
// the Link.Button does not receive a text underlineColor on surface bands (contrast)
On a band with a brand background (header/footer on main), pass
contrast="main": the link color is already that surface's on-color, without
being remapped against the page background — which would break legibility on
the band.
<Paper bgColor="main" padding="xl" gap="large" deep={0}>
<Link href="/privacidade" contrast="main" text="Privacy" />
<Link href="/termos" contrast="main" text="Terms" />
</Paper>Properties
No public props documented for Link.
Besides the props above, every component accepts the layout props (spacing, color, flex/grid, sizing, border) — not repeated here.