import { Link } from '@vega-ui/react';
<Link href='#'>Billing settings</Link>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :focus-visible
Summary
Link is the navigational text action component for inline and standalone navigation affordances. It is the default choice when the interaction changes location rather than mutating state.
Imports
import { Link, type LinkProps } from '@vega-ui/react';Exported Types
LinkProps
Basic Usage
import { Link, Text } from '@vega-ui/react';
export default function BasicUsage() {
return (
<Text size={3} style={{ width: '100%' }}>
Exports include every invoice from the selected period. Read how we calculate{' '}
<Link href='/docs/billing/taxes'>VAT for EU customers</Link>{' '}
before sharing the file with your accountant.
</Text>
);
}Variants
- Inline text link versus standalone navigational action
- Default anchor rendering versus
asChildcomposition
Edge Cases
- Use real navigation targets, not button-like behavior disguised as links.
asChildis useful for router integrations while preserving link styling.- Focus and underline behavior should be checked against surrounding typography.
- If the desired presentation is a strong call-to-action surface,
Button asChildmay be clearer than a plain text link.
Common Mistakes
- Using
Linkfor actions that mutate state. - Styling a link like a button without considering semantics.
- Using vague link text such as "Click here".
Examples
Basic
Basic: standalone link after a list
A standalone link names its destination ("View all 48 invoices") instead of "Click here". The arrow is decorative: Icon hides it from screen readers.
import { Heading, Icon, Link } from '@vega-ui/react';
import { ArrowRightIcon } from '@vega-ui/icons';
import './examples.css';
const invoices = [
{ id: 'INV-2041', amount: '$1,240.00' },
{ id: 'INV-2040', amount: '$860.50' },
{ id: 'INV-2039', amount: '$2,115.00' },
];
export default function StandaloneNavigationLink() {
return (Controlled/Stateful
Controlled/Stateful: router link with the current page
Link asChild passes its styling to a router link. The parent owns the current route and marks the active item with aria-current='page'; here the router is simulated with preventDefault.
import { type AnchorHTMLAttributes, type MouseEvent, useState } from 'react';
import { Link } from '@vega-ui/react';
interface RouterLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
to: string
onNavigate(to: string): void
}
function RouterLink({ to, onNavigate, onClick, ...props }: RouterLinkProps) {
const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
onClick?.(event);
event.preventDefault();Form/Integration
Form/Integration: forgot password link in a sign-in form
A link inside a form navigates and never submits. It sits next to the password label, after the email field in tab order, and the form reads its values with FormData on submit.
import { Button, Label, Link, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useState } from 'react';
export default function ForgotPasswordLink() {
const [signedIn, setSignedIn] = useState('');
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setSignedIn(String(new FormData(event.currentTarget).get('email')));
};
return (Layout/Overlay
Layout/Overlay: help link inside dialog copy
An inline link in the dialog description gives context without becoming one of the dialog actions. The text is linked to the dialog with aria-describedby.
import type { CSSProperties } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Link,
Text,Error
Error: recovery link in an error message
When a payment fails, the error message contains the way out. The message is announced with role='alert' and linked to the button with aria-describedby.
import { Button, HelperText, Link } from '@vega-ui/react';
import { useState } from 'react';
export default function RecoveryLinkAfterAFailedFlow() {
const [declined, setDeclined] = useState(false);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-3)', width: '100%' }}>
<Button type='button' aria-describedby={declined ? 'payment-error' : undefined} onClick={() => setDeclined(true)}>
Pay $49.00
</Button>
{declined && (Disabled
Disabled: plain text until the destination exists
Links have no disabled state, and Link has no disabled styles. While the report is not ready, render text that explains why, and switch to a real link once it can be followed.
import { Button, Link } from '@vega-ui/react';
import { useState } from 'react';
export default function UnavailableLinkAsText() {
const [ready, setReady] = useState(false);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-6)', width: '100%' }}>
<p style={{ margin: 0 }}>
{ready && <Link href='/reports/2024-q4.pdf'>Download the Q4 report</Link>}
{!ready && (
<span style={{ color: 'var(--disable-text-color)' }}>Edge
Edge: external link that opens a new tab
target='_blank' changes the context without warning, so the link says so: a decorative icon for sighted users and a VisuallyHidden note for screen readers. rel='noopener noreferrer' keeps the new page from controlling this one.
import { Icon, Link, Text, VisuallyHidden } from '@vega-ui/react';
import { ExternalLinkIcon } from '@vega-ui/icons';
export default function ExternalLinkNewTab() {
return (
<Text size={3}>
Payments are processed by{' '}
<Link href='https://stripe.com/privacy' target='_blank' rel='noopener noreferrer'>
Stripe’s privacy policy
<Icon size='2xs' style={{ marginInlineStart: 'var(--spacing-1)', verticalAlign: 'baseline' }}><ExternalLinkIcon /></Icon>
<VisuallyHidden asChild>
<span>(opens in a new tab)</span>Edge: long URL in a narrow container
A URL has no spaces to break on and overflows narrow cards. overflowWrap: 'anywhere' lets it wrap at any character.
import { Link, Text } from '@vega-ui/react';
const webhookUrl = 'https://hooks.example.com/services/T024BE7LD/B08S9Q2KJ7H/aD4f9kLq2ZxR7mN3pW8vY1cE';
export default function LongUrlWrapping() {
return (
<div style={{ width: 240, padding: 'var(--spacing-8)', border: '1px solid var(--separator-opaque)', borderRadius: 'var(--radius-4)', background: 'var(--surface-primary)' }}>
<Text size={3}>
Events are sent to{' '}
<Link href={webhookUrl} style={{ overflowWrap: 'anywhere' }}>{webhookUrl}</Link>
</Text>
</div>Anatomy
Overview
Link is a single navigational text surface. Its anatomy is one anchor-like root plus inline or standalone text content, optionally rendered through asChild.
Required Parts
Link
Required. Owns link styling and anchor semantics when rendered directly.
Optional Parts
Link does not expose public child parts.
Composition Order
Link- inline or standalone text content
Valid Composition Patterns
import { Link, Text } from '@vega-ui/react';
export default function BasicUsage() {
return (
<Text size={3} style={{ width: '100%' }}>
Exports include every invoice from the selected period. Read how we calculate{' '}
<Link href='/docs/billing/taxes'>VAT for EU customers</Link>{' '}
before sharing the file with your accountant.
</Text>
);
}Invalid Composition Patterns
Link used for state mutation
If the interaction mutates state instead of navigating, use Button.
Vague link copy
Inline text like "Click here" weakens scanability and accessibility.