Modes
Inline (default)
Flows inside a paragraph. Underlined, inherits the surrounding text's weight and size.
Read our privacy policy for details.
<Text variant="body-md">
Read our <Link href="/privacy">privacy policy</Link> for details.
</Text>External inline
Adds target="_blank" rel="noopener noreferrer" on web, auto-renders a trailing icon (web only — see Accessibility), and appends "(opens in a new tab)" to the accessible name on both platforms.
See the help centre.
<Text variant="body-md">
See the <Link href="https://checkatrade.com/help" external>help centre</Link>.
</Text>Standalone
Weight 500, md size, underlined at rest (no underline on hover, underlined again on focus). Its own element, on its own line.
<Link inline={false} onClick={() => {}}>Show all trades</Link>Standalone external
external auto-renders a fixed trailing icon — mutually exclusive with a manual trailingIcon.
Subtle
Fixed navy colour, no colour change on hover/focus, no distinct visited colour. No underline at rest — gains one on hover/focus instead. Valid on inline or standalone.
<Link inline={false} onClick={() => {}} subtle>Show all trades</Link>Inverse
Inside an inverse <Surface> this happens automatically — Surface is the source of truth. Pass inverse explicitly only for a dark or branded background that isn’t a Surface.
Inverse on a dark Surface
The rendered preview passes inverse explicitly as a docs-site-only workaround — this ComponentPreview's client:load island doesn't carry Surface's tone context to nested framework components, so Link's auto-detection can't run here. The documented API (below) needs no explicit inverse prop; Storybook and real consumer apps pick it up from Surface automatically.
<Surface tone="inverse" className="rounded-lg p-6">
<Link href="/help">Login help</Link>
</Surface>When to use
Use Link for a text link — either inline inside a sentence of body copy, or as a standalone navigation/action link on its own line. Provide exactly one of href (navigation) or onClick/onPress (action); the underlying element and accessible role follow automatically.
Modes
- Inline (default): underlined in every state, inherits the surrounding text’s weight and size. Use inside a
Textparagraph. - Standalone (
inline={false}): weight 500, md size, optionalleadingIcon/trailingIcon. Underline toggles by state (underlined at rest, none on hover, underlined again on focus) rather than being constantly on/off. Use for a link that stands on its own — not embedded in a sentence.
Read our privacy policy for details.
Do
Use inline for a link embedded mid-sentence in body copy.
Read our privacy policy for details.
Don't
Don't use a standalone link mid-paragraph — it breaks the reading flow and doesn't inherit the surrounding text size.
When not to use
- Button-styled actions: use
Buttonfor a primary/secondary call to action with a filled or outlined shell —Linkis text-only. - Icon-only actions: use
IconButton.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children * | string | - | Visible link text. String only — an inline link flows inside surrounding copy. |
| inline | boolean | true | false renders a standalone link (weight 500, md size, underline toggles by state). |
| subtle | boolean | false | Fixed navy colour, no colour change on hover/focus, no distinct visited colour. Standalone only (inline={false}) — a type error otherwise. Ignored when the link resolves to inverse. |
| href | string | - | Navigation target. XOR with onClick/onPress. Web renders a real <a href>; native opens via Linking.openURL. Role "link". |
| onClick | () => void | - | Web — action handler. XOR with href. Renders a link-styled <button>. Role "button". |
| onPress | () => void | - | Native — action handler. XOR with href. Role "button". |
| external | boolean | false | Web: adds target="_blank" rel="noopener noreferrer", auto-renders a trailing icon (both modes), appends "(opens in a new tab)" to the accessible name. Native: auto-renders a trailing icon on standalone only (no visual icon inline — see Accessibility), appends the same suffix to accessibilityLabel on both modes. Mutually exclusive with trailingIcon. |
| leadingIcon | ComponentType<SVGProps<SVGSVGElement>> (web) / ComponentType<SvgProps> (native) | - | Standalone only (inline={false}). A glyph from @checkatrade/icons. |
| trailingIcon | ComponentType<SVGProps<SVGSVGElement>> (web) / ComponentType<SvgProps> (native) | - | Standalone only (inline={false}); mutually exclusive with external. A glyph from @checkatrade/icons. |
| inverse | boolean | surrounding Surface tone | Swaps to inverse token colours. Defaults to the surrounding Surface tone — pass explicitly to force it on a dark background that is not a Surface. |
| aria-label | string | children | Web — accessible-name override. |
| accessibilityLabel | string | children | Native — accessible-name override. |
| data-testid | string | - | Web — passed to the root element for test targeting. |
| testID | string | - | Native — passed to the root element for test targeting. |
Import
Web
import { Link } from '@checkatrade/components-web';
import '@checkatrade/components-web/css';Native
import { Link } from '@checkatrade/components-native';Basic usage
<Text variant="body-md">
Read our <Link href="/privacy">privacy policy</Link> for details.
</Text>
<Link inline={false} onClick={() => {}}>
Show all trades
</Link>Colour
Real link colour tokens landed in @checkatrade/tokens 0.14.0 — theme-aware (dark mode swaps
the default/inverse ramps automatically, no manual workaround needed):
- Default (not
subtle, notinverse): rest#5263ff, hover/focus#3a3787, visited (web only)#681da8. - Inverse: rest
#ffffff, hover/focus#f7f7f6, visited (web only)#fdf4ff. - Subtle: fixed
#040154always (theaction-label-ghostramp — this is what the whole component used everywhere before 0.14.0) — no colour change on hover/focus, no distinct visited colour. Standalone only (inline={false}) — a type error otherwise. Not available combined withinverse— silently ignored if the link resolves to inverse (Figma has no subtle+inverse variant).
Visited links (web only)
href-based links use the real browser :visited pseudo-class automatically — no prop.
Colour comes from the real visited tokens above. Skipped entirely when subtle (no distinct
visited look). Native has no history API, so there’s no visited support there.
Browsers only allow a handful of colour properties (including color) to change under
:visited, for privacy reasons — text-decoration-line isn’t one of them, so a visited
standalone link’s underline can’t react to visited state; it keeps whatever the rest state’s
underline already is.
Platform status
| Platform / Area | Status |
|---|---|
| Design (Figma) | Beta |
| Web (React) | Beta |
| Native (React Native) | Beta |
| iOS (Swift) | Planned |
| Android (Kotlin) | Planned |
| Accessibility audit | Planned |
Accessibility
Web
hrefrenders a real<a href>— announced as a link; supports cmd/right-click.onClickrenders a<button>— announced as a button.- Inline links are underlined in every state so they’re distinguishable by more than colour
(WCAG). Standalone’s underline toggles by state instead (underlined at rest, none on
hover, underlined again on focus — inverted when
subtle). - Keyboard-only focus ring via a scoped
[data-cat-link]:focus-visiblerule. externalrenders a visual icon on both inline and standalone (browsers support inline SVG inside<a>).
React Native
href→accessibilityRole="link", opens viaLinking.openURL.onPress→accessibilityRole="button".- Inline renders as a nested
Textnode so it flows inside the surrounding paragraph. - Standalone renders as its own
Pressablerow with a keyboard-only focus ring on react-native-web. externalrenders a visual icon on standalone only — RN’s text-flow engine only inline-nestsText/Image, not an SVG/View component, so an inline external link has no visual icon on native (an architectural RN limit, not a styling gap).
Both platforms
- Tone is conveyed by an underline (inline, or standalone at rest/focus) in addition to colour.
externallinks get “(opens in a new tab)” appended to the accessible name on every platform and mode, even where the visual icon can’t render (native inline) — the accessibility warning is never platform-gated, only the icon is.- A visited standalone link’s underline can’t react to visited state — browsers only allow a
handful of colour properties (including
color) to change under:visited, nevertext-decoration-line. This is a browser privacy restriction, not a bug.
No releases yet.