Skip to content
v2.5.0
English
Русский
Documentation

Link

Link is the navigational text action component for inline and standalone navigation affordances.
link.tsx · 3 lines
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
Tested in
14 tests

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

basic-usage.tsx · 11 lines
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 asChild composition

Edge Cases

  • Use real navigation targets, not button-like behavior disguised as links.
  • asChild is 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 asChild may be clearer than a plain text link.

Common Mistakes

  • Using Link for actions that mutate state.
  • Styling a link like a button without considering semantics.
  • Using vague link text such as "Click here".

Examples

Basic

A standalone link names its destination ("View all 48 invoices") instead of "Click here". The arrow is decorative: Icon hides it from screen readers.

standalone-navigation-link.tsx · 38 lines
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

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.

router-composed-link.tsx · 55 lines
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

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.

forgot-password-link.tsx · 39 lines
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

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.

inline-help-link-inside-dialog-copy.tsx · 52 lines
import type { CSSProperties } from 'react';
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,
  IconButton,
  Link,
  Text,

Error

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.

recovery-link-after-a-failed-flow.tsx · 21 lines
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.

unavailable-link-as-text.tsx · 22 lines
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

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.

external-link-new-tab.tsx · 18 lines
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.

long-url-wrapping.tsx · 14 lines
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

Required. Owns link styling and anchor semantics when rendered directly.

Optional Parts

Link does not expose public child parts.

Composition Order

  1. Link
  2. inline or standalone text content

Valid Composition Patterns

basic-usage.tsx · 11 lines
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

If the interaction mutates state instead of navigating, use Button.

Inline text like "Click here" weakens scanability and accessibility.