# Text Link
An anchor link for navigating to pages or anchor points within prose
Section: components
Docs: https://nebula.kaluza.com/components/text-link
---
## When to use
- For navigation links embedded within body text, such as references, further reading, or related pages
- For standalone navigation links where a full button-style target is not appropriate
- When linking to an anchor point within the same page
## When not to use
- **To trigger an action such as submitting a form or opening a modal** - links navigate; buttons act. Use [CTA Button](/components/cta-button) instead.
- **As a primary or secondary call to action** - use [CTA Link](/components/cta-link) instead, which has the visual prominence of a button while remaining an anchor element.
- **When the link label would be non-descriptive** - do not use "click here", "read more", or "find out more" as link text. Rewrite the surrounding sentence so the link label describes the destination.
## Anatomy
1. **Link text** (the visible, underlined label)
2. **External indicator** (an icon and visually hidden text appended when `opensInNewWindow` is true)
## Basic usage
```tsx
How to be a conscious consumer
```
## Variants and features
### Inline links
`TextLink` is designed to sit within prose. Place it inside a [Text](/components/text) (`P`) element so it inherits body font size and color context correctly.
The underline is always visible on `TextLink`. This is intentional: removing underlines from inline links reduces their discoverability for users with colour vision deficiencies.
```tsx
Visit Friends of the Earth for more information.
```
### Opening in a new window
Use `opensInNewWindow` when the link genuinely needs to open in a new browser tab, such as a link to an external site or a document that the user needs to reference while staying on the current page. Do not use it by default. Opening links in new windows removes the user's ability to navigate back and is unexpected behaviour for most links.
When `opensInNewWindow` is true, the component automatically appends a new-window icon and a visually hidden " (opens in new window)" text disclosure so screen reader users are informed before activating the link.
```tsx
Ofgem price cap information
```
### External links
For links to external domains, `TextLink` automatically adds `rel="noopener"` to prevent the linked page from accessing the opener's `window` object. This happens based on whether the `href` is detected as an external link. No extra configuration is needed.
### React Router integration
Pass the React Router `Link` component via the `as` prop to use client-side navigation while keeping the visual style of `TextLink`.
This example is not live, because the docs site does not use React Router.
```tsx
import { Link } from 'react-router-dom';
View account
```
## Rules and guidance
### Usage
- **TextLink** MUST have an href prop set to a valid URL or anchor reference. Rationale: TextLink renders an anchor element. An anchor without href is not keyboard-focusable and is not treated as a link by browsers or assistive technology. [id: textlink.href-required; testability: auto]
- **TextLink** MUST NOT be used to trigger in-page actions such as submitting a form, opening a modal, or toggling state. Rationale: Links navigate; buttons act. Using a link for an action breaks the user's mental model and can break keyboard and screen reader interaction. Use CTAButton instead. [id: textlink.navigation-not-action; testability: manual]
- **TextLink** MUST NOT have its underline removed. Rationale: Underline is the primary visual differentiator for inline links, especially for users who cannot distinguish by colour alone. Removing it fails WCAG 2.2 SC 1.4.1. [id: textlink.underline-always-visible; testability: auto]
- **opensInNewWindow** SHOULD NOT be set to true by default on all external links. Rationale: Opening links in new tabs removes the user's ability to navigate back and is unexpected unless there is a clear reason (e.g. the user needs to reference a document while completing a task). [id: textlink.opens-in-new-window-sparingly; testability: manual]
- **TextLink** SHOULD NOT be used as a primary or secondary call to action. Rationale: CTALink provides button-style prominence for call-to-action navigation. TextLink is for inline or supplementary links. [id: textlink.use-cta-link-for-cta; testability: manual]
### Placement
- **TextLink** SHOULD be placed inside a P element when used as an inline prose link. Rationale: TextLink inherits font size from its parent. Placing it outside a P can result in mismatched sizing. [id: textlink.inside-prose; testability: manual]
### Content
- **Link text** MUST describe the destination or purpose of the link, not just invite interaction. Rationale: Non-descriptive labels such as "click here" or "read more" are meaningless to screen reader users who navigate by link text. [id: textlink.descriptive-label; testability: manual]
- **Link text** SHOULD be unique on the page when links navigate to different destinations. Rationale: Screen reader users may browse a list of all links on the page. Identical labels for different destinations are ambiguous. [id: textlink.unique-labels; testability: manual]
### Interaction
- **TextLink** WILL add rel="noopener" to any link whose href is an external URL. The component detects external links automatically and appends noopener to the rel attribute. [id: textlink.external-link-noopener; testability: auto]
- **TextLink** WILL append a visually hidden " (opens in new window)" text string and a new-window icon when opensInNewWindow is true. The component sets target="_blank" and renders both additions automatically. [id: textlink.new-window-disclosure-automatic; testability: auto]
- **TextLink** WILL display a visible focus ring when focused via keyboard. The component applies the Nebula focusRing style via :focus-visible automatically. [id: textlink.focus-ring; testability: manual]
- **TextLink** MUST use the opensInNewWindow prop rather than manually setting target="_blank" when a link should open in a new tab. Rationale: The opensInNewWindow prop automatically appends a visually hidden " (opens in new window)" disclosure and the new-window icon. Setting target="_blank" manually omits this disclosure. [id: textlink.opens-in-new-window-disclosure; testability: auto]
## Accessibility
### In brief
Text Link is a native `a` element. A screen reader announces it as a link with its text, and a keyboard user reaches it with `Tab` and follows it with `Enter`.
### What Nebula provides
- The element is an `a`, and it stays an `a` when rendered as a React Router `Link` through `as`.
- The link text is always underlined and takes its colour from the theme's link colour.
- A visible focus ring shows when the link is focused with the keyboard.
- With `opensInNewWindow`, the link opens in a new tab, shows an icon hidden from screen readers and adds the hidden text "(opens in new window)" to the link.
### Keyboard behaviour
A link is one tab stop. Reach it with `Tab` and follow it with `Enter`.
#### Interactions
- **Tab** - Moves focus to the link
- **Enter** - Follows the link
### Accessible design
#### Write link text that makes sense out of context
A screen reader user can list every link on a page, so "Read more" says nothing there. Name the destination, for example "Read about tracking carbon".
WCAG: [2.4.4 Link Purpose (In Context)](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html)
#### Don't remove the underline or change the link colour
The underline is what tells the link from the text around it for someone who cannot tell the colours apart, and the theme colour is the one that meets the contrast ratio on the page.
WCAG: [1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) [1.4.3 Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
#### Use a link to go somewhere and a button to do something
A link is announced as a link and a button as a button, and a user expects the first to navigate and the second to act.
WCAG: [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html)
### Accessible development
#### Always pass an `href`, or `to` when rendering a router `Link`
An anchor without a destination is not a link: it is not a tab stop and a screen reader does not announce it as one.
WCAG: [2.1.1 Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html)
#### If a link opens in a new window, pass `opensInNewWindow`
The prop adds the icon and the hidden warning. Setting `target="_blank"` yourself opens the window but adds neither, so a screen reader user is not told.
### Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
| --- | --- | --- |
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Partially tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What each test covers:
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
What these statuses mean:
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
## Properties
### TextLink
- `opensInNewWindow` (type `boolean`, optional, default `false`). Opens the link in a new tab (sets `target="_blank"`), shows a new-window icon and adds a visually hidden "(opens in new window)" message for screen readers.
## Styles
Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS.
### a
```css
text-decoration: none;
font-weight: inherit;
color: ${colors.link};
&:hover {
color: ${colors.primaryAlt};
}
&:focus-visible {
${focusRing}
}
```
### span
```css
text-decoration: underline;
```
### icon
```css
display: inline-flex;
vertical-align: middle;
```