# Link `Link` (`shared/components/Link/Link.tsx`) is the site's anchor component, built from the BDS Link spec (`components/link.md` / `link.json` / `link-examples.md` in `pd-xrpl-developer-docs`). It's the only anchor that should be used for new JSX anchor tags going forward. The old design-system link, `BdsLink`, is kept intact but unused — see [`legacy/BdsLink.md`](legacy/BdsLink.md). A live reference of every variety (size × color group × with/without icon, light and dark) is at `/link-demo` ([link-demo.page.tsx](../../../link-demo.page.tsx)), each example labeled with the spec entry it matches. ## Usage ```tsx import { Link } from 'shared/components/Link'; // or, in files that already import Redocly's routing Link: import { Link as XrplLink } from 'shared/components/Link'; Read More

Body copy with an inline link in a sentence.

``` `Link` always renders a real `` — but for anything that isn't an external URL (`http(s):`/`mailto:`) or a bare `#fragment`, that `` comes from Redocly's own routing `Link` (`@redocly/theme/components/Link/Link`), not a plain one. This matters, not just cosmetically: Redocly's `Link` resolves relative paths against the current route, rewrites the path for the current locale (so `href="/docs/concepts/"` correctly becomes `/ja/docs/concepts/` for a `/ja/...` visitor instead of silently bouncing them to the English page), and drives client-side navigation plus hover-prefetch instead of a full page load. Writing `href="/docs/concepts/"` gets all of that automatically — there's no separate "internal" prop to set. A control that acts rather than navigates is a `Button`, even if it's meant to look like a link. ## Props | Prop | Values | Default | Notes | |---|---|---|---| | `intention` | `brand` \| `neutral` | `brand` | `brand` = XRPL green. `neutral` = black/white, no distinct hover, sage `:visited`. | | `context` | `on-theme` \| `on-inverse` \| `on-saturated` | `on-theme` | Which surface the link sits on — controls both color group and focus-ring color. `neutral` + `on-saturated` is a compile-time type error (no such combination exists in the spec). | | `variation` | `inline` \| `standalone` | `inline` | `inline` sits inside running text: no fixed size (inherits the parent's font-size/line-height), no icon, `display: inline` so it wraps. `standalone` is a link acting as its own element (a card's "Read More", a CTA under a paragraph): fixed size, optional trailing icon, `display: inline-flex`. | | `size` | `sm` \| `md` \| `lg` | `md` | Only affects `standalone` — `inline` always inherits from its parent. | | `iconEnd` | `boolean` | `false` | Trailing arrow, from the shared icon set (`shared/components/Icons`) -- `XrplArrowInternalLinkIcon`, or `XrplArrowExternalLinkIcon` when `target="_blank"` is set. Only rendered when `variation="standalone"`; ignored on `inline`. | | `href` | `string` | — | Required. | Everything else (`target`, `rel`, `onClick`, `aria-*`, ...) passes through to the ``. There's no `disabled` state — the old component had one, but the new spec forbids it (ADR-XRPL-012): a disabled link is a contradiction, since a link that can't be followed shouldn't render as a link. ## Color groups → intention × context | `intention` | `context` | Result | |---|---|---| | `brand` | `on-theme` | XRPL green rest/hover, sage `:visited` | | `brand` | `on-inverse` | Green-on-dark-surface variant (e.g. a link inside a dark card on a light page) | | `brand` | `on-saturated` | Black-on-green (e.g. a link inside a solid-green banner) | | `neutral` | `on-theme` | Black (light) / white (dark), flat across hover/active, sage `:visited` | | `neutral` | `on-inverse` | Neutral tuned for a dark card on a light page | Every resolved value lives in one place: [`styles/_link-tokens.scss`](../../../styles/_link-tokens.scss)'s `xrpl-link-colors($group)` mixin. `Link`'s own stylesheet ([`_link.scss`](_link.scss)) just calls it per `--intention` class (`.xrpl-link--brand`, `.xrpl-link--neutral-on-inverse`, etc.) — it never hardcodes a color itself. Change a token once here and both `Link` and the sitewide fallback (below) pick it up. `neutral`'s `:hover`/`:active` are intentionally flattened to match its rest color — that's a deliberate design decision (a black link doesn't get a color change on hover), not an incomplete state. `:visited` is not flattened for either group and follows the spec's sage values: `neutral` uses `$sage-11`/ `$sage-dark-11` (a muted gray, clearly distinct from rest), `brand` uses `$sage-12`/`$sage-dark-12` (legitimately near-black/near-white). Both are correct per spec, not bugs, and can only be seen with real browsing history — not in an incognito/sandboxed browser, and not on `/link-demo`'s links directly (see the reset button there, which points every demo link at a fresh, never-visited URL instead). ## What plain `` tags get (the fallback) Most of the site's links aren't JSX — they're `[text](url)` or raw `` inside `.md`/`.mdx` files. Two mechanisms make sure those still get themed, without every markdown file needing to import a component: 1. **`MarkdownLink` override** — `@theme/components/MarkdownLink.tsx` replaces Redocly's default renderer for markdoc's `[text](url)` syntax (registered in `@theme/markdoc/components.tsx`). It still renders Redocly's own routing `Link` (so client-side navigation keeps working) but stamps it with `Link`'s class scheme via the exported `linkClassName()` helper, forced to `intention="neutral" variation="inline"` — docs/technical body copy stays black, not brand green, per design direction. This covers the overwhelming majority of markdown links site-wide with no per-file changes. 2. **CSS-cascade fallback** — [`styles/_content.scss`](../../../styles/_content.scss)'s `a:not(.xrpl-link)` rule, scoped to `[data-component-name="Markdown/Markdown"] article`, applies the same neutral/on-theme mixins directly. This is the safety net for anything the `MarkdownLink` override doesn't reach: raw HTML `` embedded directly in a `.md` file (markdoc passes those through as literal HTML, bypassing the `link` node entirely) and tag components that render their own `` (e.g. `{% child-pages %}` → `ChildPages.tsx`). Both paths call the exact same `_link-tokens.scss` mixins `Link` itself uses, so there's one source of truth for color/underline/focus-ring values — nothing is hand-duplicated. If you're building raw HTML strings that can't use JSX at all (e.g. a data array rendered with `dangerouslySetInnerHTML`, like the FAQ answers in [`about/index.page.tsx`](../../../about/index.page.tsx)), you can opt a specific link into a non-neutral treatment by writing `linkClassName()`'s output literally into the string: ```html ... ``` Anything without an `xrpl-link` class at all still falls through to the neutral fallback above — it never renders unstyled. ## Adding a new `:not(.xrpl-link)` site A handful of older, page/component-scoped stylesheets style `` tags directly (landing-page body copy, FAQ accordions, card descriptions, etc.), predating this component. Each one needs a `:not(.xrpl-link)` exclusion so it doesn't fight the component's own color rules on specificity — otherwise a `Link` rendered inside that context can get silently overridden (this has happened several times: `_landings.scss`, `_light-theme.scss`'s separate `.landing` block, `_pages.scss`'s FAQ rule, `CardTextIconCard.scss`'s description-link rule). If you add a new CSS rule that targets `a` by tag inside some content area, exclude `.xrpl-link` from it, the same way the existing ones do. ## Specificity gotcha: `:where()` and `bds-theme-mode` `_link-tokens.scss`'s mixins wrap **both** light and dark branches in an explicit `bds-theme-mode(light)` / `bds-theme-mode(dark)` call — neither is left as an unscoped default. This isn't stylistic: an unscoped `.xrpl-link--brand { color: ... }` is `(0,1,0)` specificity, and it loses to `_light-theme.scss`'s generic `html.light a:where(:not(.bds-link)...)` fallback, which resolves to `(0,1,2)` — `:where()` zeroes the specificity of selectors *inside* it, but not `html`/`.light` sitting outside it. Wrapping both branches the same way brings every `Link` color rule to `(0,2,1)`, which wins cleanly without `!important`. Keep this pattern if you touch these mixins. ## Files - `Link.tsx` — component, types, `linkClassName()` helper - `_link.scss` — geometry, typography, display, triggers the icon's built-in motion (see below) - `../Icons` — `XrplArrowInternalLinkIcon` / `XrplArrowExternalLinkIcon`, the trailing arrow itself: artwork, sizing (`1em`, tracks this element's own font-size), and the hover/focus-visible motion, none of which lives in this component. See `shared/components/Icons/shared.scss` for the `bds-icon-engaged` contract and `/icon-demo` for a live reference. - `../../../styles/_link-tokens.scss` — color/underline/focus mixins (shared with the fallback) - `../../../@theme/components/MarkdownLink.tsx` — markdown `[text](url)` override - `../../../styles/_content.scss` — raw-HTML-in-markdown fallback - `../../../link-demo.page.tsx` — visual reference of every variant - `legacy/BdsLink.tsx`, `legacy/LinkArrow.tsx` — old component, unused, kept for reference