On this page
Every color and size on this site comes from a design token.
LinkExample.tsx
import { Link } from "@/components/Link";
export function LinkExample() {
return (
<>
<p>
Every color and size on this site comes from a{" "}
<Link href="/design-system/tokens">design token</Link>.
</p>
<p>
<Link href="https://github.com/BeMorr16/Santoryu" newTab>
Source code on GitHub
</Link>
</p>
</>
);
}Examples
Inline, in running text
Read about the design tokens, or email hello@example.com.
New tab
The code for this site is on GitHub, opens in new tab.
Visited (opt-in)
Design System and Design tokens
Visited links turn violet.
Current page
This page
Announced as: “This page, link, current page”
This page, unmarked
Announced as: “This page, unmarked, link”
Underline on hover
No underline
Customized with a component token
Quiet links, like this token reference, keep the underline but take the text color.
CSS
.quietLinks {
--component-link-color: var(--color-text-default);
--component-link-color-hover: var(--color-link-hover);
}Props
Any native prop not listed here goes to the root element unchanged.
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Required. A /path routes client-side; anything else is a plain link. |
newTab | boolean | false | Opens in a new tab, with an icon and “, opens in new tab” in its name. |
rel | string | — | Merged with noopener noreferrer when newTab is set, e.g. rel="me". |
appearance | "inline" | "button" | "inline" | inline is an underlined text link; button looks like a Button. |
showVisited | boolean | false | Inline only. Colors the link violet once visited. |
currentMatch | "exact" | "section" | "exact" | Inline only. Routed inline links set aria-current themselves: "page" on an exact match, and with "section", "true" on pages below it. Your own aria-current overrides it. The hash is ignored, so use #id hrefs for in-page links. |
underline | "always" | "hover" | "none" | "always" | Where the underline shows. Inline links only. |
variant | "primary" | "secondary" | "ghost" | "secondary" | With appearance="button" only. |
size | "sm" | "md" | "md" | With appearance="button" only. |
iconStart | IconName | — | With appearance="button" only. |
iconEnd | IconName | — | With appearance="button" only. |
nameTooltip | boolean | true | With appearance="button" only. An icon-only link shows its name (without the new-tab text) as a tooltip; false turns it off. None for a link named by aria-labelledby, whose name is already on screen. |
prefetch | "intent" | "render" | "none" | "viewport" | — | React Router's option, for /path links only (ignored otherwise). |
replace | boolean | — | React Router's option, for /path links only (ignored otherwise). |
state | any | — | React Router's option, for /path links only (ignored otherwise). |
preventScrollReset | boolean | — | React Router's option, for /path links only (ignored otherwise). |
viewTransition | boolean | — | React Router's option, for /path links only (ignored otherwise). |
reloadDocument | boolean | — | React Router's option, for /path links only (ignored otherwise). |
discover | "render" | "none" | — | React Router's option, for /path links only (ignored otherwise). |
Component tokens
| Token | Copy | Description | Aliases | Light | Dark | Preview |
|---|---|---|---|---|---|---|
--component-link-color | Inline link text. | color.link.default | #795616from color.gold.700 | #d3c2a2from color.champagne.400 | ||
--component-link-color-hover | Inline link text on hover. | color.link.hover | #62440afrom color.gold.800 | #e4d2b2from color.champagne.300 | ||
--component-link-color-current | Text of a link to the current page (aria-current). | color.text.default | #17202bfrom color.gray.900 | #fffffffrom color.gray.0 | ||
--component-link-color-visited | Text of a visited link, where showVisited is set. | color.link.visited | #5b3fa6from color.violet.600 | #d9cdf7from color.violet.200 | ||
--component-link-underline-thickness | Underline thickness. | effect.underline.thickness | 1pxfrom effect.line.1 | 1pxfrom effect.line.1 | ||
--component-link-underline-thickness-hover | Underline thickness on hover. | effect.underline.thickness-hover | 2pxfrom effect.line.2 | 2pxfrom effect.line.2 | ||
--component-link-underline-offset | Gap between the text and its underline. | effect.underline.offset | 2pxfrom effect.line.2 | 2pxfrom effect.line.2 | ||
--component-link-font-weight-current | Weight of a link to the current page, so it's marked by more than color. | type.weight.strong | 600from type.font-weight.600 | 600from type.font-weight.600 | ||
--component-link-focus-ring-color | Focus indicator. It's drawn outside the link, so it's checked against the surfaces. | color.focus.ring | #005f7bfrom color.cyan.700 | #54d8f4from color.cyan.300 | ||
--component-link-new-tab-gap | Gap between an inline link's text and its new-tab icon. | space.inline.xs | 0.25remfrom space.1 | 0.25remfrom space.1 | ||
--component-link-transition-duration | Duration of hover color changes (0ms under reduced motion). | motion.duration.instant | 100msfrom motion.time.100Reduced motion: 0ms | 100msfrom motion.time.100 | No preview | |
--component-link-transition-easing | Easing of hover color changes. | motion.easing.standard | cubic-bezier(0.4, 0, 0.2, 1)from motion.curve.in-out | cubic-bezier(0.4, 0, 0.2, 1)from motion.curve.in-out | No preview |
Accessibility
- Always an <a> with an href; there is no disabled link.
- Always named: inline links need text, and a button-styled link with no text needs an icon plus aria-label or aria-labelledby (a type error otherwise). Types can't see empty children such as {undefined} or a lone decorative icon, so those still need care.
- Routed inline links mark the current page with aria-current="page", and with currentMatch="section", pages below it with "true". Pass aria-current to override, including false. Button-styled links are only marked when you pass it.
- The current page is shown by weight as well as color.
- Inline links are underlined, so they're told apart from text by more than color. Hover thickens the underline.
- underline="hover" drops the underline at rest. Use it only where the position marks the links (a nav, a site name), since color alone can't (WCAG 1.4.1). A current link keeps its underline.
- underline="none" never underlines, even when current or hovered. Use it only where the position marks the links and something besides color marks the current one, such as the site bar's brackets (WCAG 1.4.1). A current link is still bold.
- The visited color is opt-in and color-only, so nothing depends on it.
- New-tab links end their name with “, opens in new tab”, also when named by aria-label or aria-labelledby, and the icon stays on the line of the last word.
- The focus ring uses component.link.focus-ring-color.
| Key | Action |
|---|---|
| Enter | Follows the link. |