# Link

> A navigation component that handles both internal and external links with customizable browser behavior.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/link
- Markdown: https://ui.ahmedbna.com/docs/components/link.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/link.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/link.json
- Install: `npx bna-ui add link`
- npm dependencies: `expo-router`, `expo-web-browser`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `text`
- Preview recording: https://demo.ahmedbna.com/0183-link-demo.MP4

---

**Example:** A basic link component with internal and external navigation

```tsx
// components/demo/link/link-demo.tsx
import { Link } from '@/components/ui/link';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkDemo() {
  return (
    <View style={{ gap: 12 }}>
      <Link href='/'>Go to Profile</Link>
      <Link href='/'>Settings</Link>
      <Link href={{ pathname: '/', params: { id: '123' } }}>User Details</Link>
    </View>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add link
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install expo-router expo-web-browser
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/link.tsx
import { Text } from '@/components/ui/text';
import { Link as ERLink, Href } from 'expo-router';
import { openBrowserAsync } from 'expo-web-browser';
import { type ComponentProps } from 'react';
import { Linking, Platform } from 'react-native';

export interface LinkProps extends Omit<ComponentProps<typeof ERLink>, 'href'> {
  href: Href;
  asChild?: boolean;
  browser?: 'in-app' | 'external';
  children: React.ReactNode;
}

// Helper function to determine if URL is external
const isExternalUrl = (href: Href): boolean => {
  // If href is an object, it's always internal navigation
  if (typeof href === 'object') {
    return false;
  }

  // Check if string href is external
  return (
    href.startsWith('http://') ||
    href.startsWith('https://') ||
    href.startsWith('mailto:') ||
    href.startsWith('tel:') ||
    href.startsWith('sms:') ||
    href.startsWith('whatsapp:') ||
    href.startsWith('ftp://') ||
    href.startsWith('file://')
  );
};

// Helper function to determine if URL should use native app (not browser)
const isNativeAppUrl = (href: string): boolean => {
  return (
    href.startsWith('mailto:') ||
    href.startsWith('tel:') ||
    href.startsWith('sms:') ||
    href.startsWith('whatsapp:')
  );
};

// Helper function to convert href to string for external links
const getHrefString = (href: Href): string => {
  if (typeof href === 'string') {
    return href;
  }

  // For object hrefs, we shouldn't convert to string for external use
  // This should only be called for external URLs (which are always strings)
  throw new Error('Cannot convert object href to string for external use');
};

export function Link({
  href,
  asChild = false,
  children,
  browser = 'in-app',
  ...rest
}: LinkProps) {
  const isExternal = isExternalUrl(href);

  const handlePress = async (event: any) => {
    if (isExternal) {
      // Always prevent default for external links
      event.preventDefault();

      const hrefString = getHrefString(href);

      if (Platform.OS !== 'web') {
        // Check if this is a native app URL (email, phone, etc.)
        if (isNativeAppUrl(hrefString)) {
          // Always use Linking.openURL for native app URLs
          try {
            const canOpen = await Linking.canOpenURL(hrefString);
            if (canOpen) {
              await Linking.openURL(hrefString);
            } else {
              console.warn(`Cannot open URL: ${hrefString}`);
              // Optionally show an alert to the user
            }
          } catch (error) {
            console.error('Error opening URL:', error);
          }
        } else {
          // For HTTP/HTTPS URLs, use browser preference
          if (browser === 'external') {
            // Open the link in external browser
            await Linking.openURL(hrefString);
          } else {
            // Open the link in in-app browser (default)
            try {
              await openBrowserAsync(hrefString);
            } catch (error) {
              console.error('Error opening browser:', error);
              // Fallback to external browser
              await Linking.openURL(hrefString);
            }
          }
        }
      } else {
        // On web platform
        if (isNativeAppUrl(hrefString)) {
          // For web, directly navigate to the URL (browser will handle it)
          window.location.href = hrefString;
        } else {
          // For HTTP/HTTPS URLs, open in new tab
          window.open(hrefString, '_blank');
        }
      }
    }
    // For internal navigation, don't prevent default - let ERLink handle it
  };

  // For external links, use a custom approach to avoid conflicts
  if (isExternal) {
    return (
      <ERLink asChild={asChild} href={href} onPress={handlePress} {...rest}>
        {typeof children === 'string' ? (
          <Text variant='link'>{children}</Text>
        ) : (
          children
        )}
      </ERLink>
    );
  }

  // For internal links, use ERLink directly without custom onPress
  return (
    <ERLink asChild={asChild} href={href} {...rest}>
      {typeof children === 'string' ? (
        <Text variant='link'>{children}</Text>
      ) : (
        children
      )}
    </ERLink>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { Link } from '@/components/ui/link';
```

```tsx
<Link href='/profile'>Go to Profile</Link>
```

```tsx
<Link href='https://example.com' browser='external'>
  Open in Browser
</Link>
```

## Examples

#### Default Internal Navigation

**Example:** Basic internal navigation links

```tsx
// components/demo/link/link-demo.tsx
import { Link } from '@/components/ui/link';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkDemo() {
  return (
    <View style={{ gap: 12 }}>
      <Link href='/'>Go to Profile</Link>
      <Link href='/'>Settings</Link>
      <Link href={{ pathname: '/', params: { id: '123' } }}>User Details</Link>
    </View>
  );
}
```

#### External Links

**Example:** Links that open external URLs

```tsx
// components/demo/link/link-external.tsx
import { Link } from '@/components/ui/link';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkExternal() {
  return (
    <View style={{ gap: 12 }}>
      <Link href='https://github.com'>Visit GitHub</Link>
      <Link href='https://expo.dev'>Expo Documentation</Link>
      <Link href='https://reactnative.dev'>React Native Docs</Link>
    </View>
  );
}
```

#### Browser Options

**Example:** Links with different browser opening behaviors

```tsx
// components/demo/link/link-browser.tsx
import { Link } from '@/components/ui/link';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkBrowser() {
  return (
    <View style={{ gap: 16 }}>
      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>
          In-App Browser (Default)
        </Text>
        <View style={{ gap: 6 }}>
          <Link href='https://github.com' browser='in-app'>
            Open GitHub in-app
          </Link>
          <Link href='https://expo.dev'>Open Expo docs in-app</Link>
        </View>
      </View>

      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>
          External Browser
        </Text>
        <View style={{ gap: 6 }}>
          <Link href='https://github.com' browser='external'>
            Open GitHub externally
          </Link>
          <Link href='https://expo.dev' browser='external'>
            Open Expo docs externally
          </Link>
        </View>
      </View>
    </View>
  );
}
```

#### With Custom Children

**Example:** Links with custom child components instead of text

```tsx
// components/demo/link/link-custom.tsx
import { Button } from '@/components/ui/button';
import { Link } from '@/components/ui/link';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { ExternalLink, HomeIcon, Mail } from 'lucide-react-native';
import React from 'react';

export function LinkCustom() {
  return (
    <View style={{ gap: 16 }}>
      <Link href='/' asChild>
        <Button icon={HomeIcon}>Welcome</Button>
      </Link>

      <Link href='https://github.com'>
        <View
          style={{
            width: '100%',
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent: 'center',
            gap: 8,
            padding: 12,
            backgroundColor: 'red',
            borderRadius: 8,
          }}
        >
          <ExternalLink size={16} color='#fff' />
          <Text style={{ color: '#fff', textAlign: 'center' }}>
            External Link
          </Text>
        </View>
      </Link>

      <Link href='mailto:contact@example.com' asChild>
        <Button variant='success' icon={Mail}>
          Send Email
        </Button>
      </Link>
    </View>
  );
}
```

#### Different Link Types

**Example:** Various types of links including mailto and tel

```tsx
// components/demo/link/link-types.tsx
import { Link } from '@/components/ui/link';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkTypes() {
  return (
    <View style={{ gap: 16 }}>
      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>
          Internal Navigation
        </Text>
        <View style={{ gap: 6 }}>
          <Link href='/'>Home Page</Link>
          <Link href='/'>About Us</Link>
          <Link href={{ pathname: '/', params: { id: '123' } }}>
            Product Details
          </Link>
        </View>
      </View>

      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>External URLs</Text>
        <View style={{ gap: 6 }}>
          <Link href='https://google.com'>Google</Link>
          <Link href='http://example.com'>Example Site</Link>
        </View>
      </View>

      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>
          Communication Links
        </Text>
        <View style={{ gap: 6 }}>
          <Link href='mailto:hello@example.com'>Send Email</Link>
          <Link href='tel:+1234567890'>Call Phone</Link>
          <Link href='mailto:support@company.com?subject=Help Request'>
            Email with Subject
          </Link>
          <Link href='sms:+1234567890'>Send SMS</Link>
        </View>
      </View>
    </View>
  );
}
```

#### Styled Links

**Example:** Links with custom styling and variants

```tsx
// components/demo/link/link-styled.tsx
import { Link } from '@/components/ui/link';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function LinkStyled() {
  return (
    <View style={{ gap: 16 }}>
      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>Default Styled</Text>
        <View style={{ gap: 6 }}>
          <Link href='/'>Default Link Style</Link>
          <Link href='https://example.com'>External Link</Link>
        </View>
      </View>

      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>
          Custom Text Styling
        </Text>
        <View style={{ gap: 6 }}>
          <Link href='/'>
            <Text
              style={{
                color: '#dc2626',
                fontWeight: '600',
                textDecorationLine: 'underline',
              }}
            >
              Red Bold Link
            </Text>
          </Link>

          <Link href='/'>
            <Text
              style={{
                color: '#059669',
                fontSize: 18,
                fontStyle: 'italic',
              }}
            >
              Green Italic Link
            </Text>
          </Link>

          <Link href='https://github.com'>
            <Text
              style={{
                color: '#7c3aed',
                fontWeight: '700',
                textTransform: 'uppercase',
                letterSpacing: 1,
              }}
            >
              Purple Uppercase
            </Text>
          </Link>
        </View>
      </View>

      <View style={{ gap: 8 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>Inline Links</Text>
        <Text>
          This is a paragraph with an <Link href='/'>inline link</Link> that
          flows naturally with the text. You can also have{' '}
          <Link href='https://example.com'>external inline links</Link> in your
          content.
        </Text>
      </View>
    </View>
  );
}
```

#### Button-Style Links

**Example:** Links styled as buttons for navigation

```tsx
// components/demo/link/link-buttons.tsx
import { Button } from '@/components/ui/button';
import { Link } from '@/components/ui/link';
import { View } from '@/components/ui/view';
import { ExternalLink, Settings, User } from 'lucide-react-native';
import React from 'react';

export function LinkButtons() {
  return (
    <View style={{ gap: 16 }}>
      <View style={{ gap: 12 }}>
        <Link href='/' asChild>
          <Button variant='default' icon={User}>
            View Profile
          </Button>
        </Link>

        <Link href='/' asChild>
          <Button variant='outline' icon={Settings}>
            Open Settings
          </Button>
        </Link>

        <Link href='https://github.com' browser='external' asChild>
          <Button variant='secondary' icon={ExternalLink}>
            Visit GitHub
          </Button>
        </Link>
      </View>

      <View style={{ flexDirection: 'row', gap: 12, flexWrap: 'wrap' }}>
        <Link href='/' asChild>
          <Button variant='default' size='sm'>
            Dashboard
          </Button>
        </Link>

        <Link href='/' asChild>
          <Button variant='ghost' size='sm'>
            Help
          </Button>
        </Link>
      </View>

      <View style={{ gap: 8 }}>
        <Link href='/' asChild>
          <Button variant='destructive' size='lg'>
            Danger Zone
          </Button>
        </Link>

        <Link href='/' asChild>
          <Button variant='success' size='lg'>
            Success Action
          </Button>
        </Link>
      </View>

      <Link href='mailto:support@example.com' asChild>
        <Button variant='link' size='sm'>
          Contact
        </Button>
      </Link>
    </View>
  );
}
```

## API Reference

### Link

The main link component that handles navigation and external URL opening.

| Prop       | Type                     | Default    | Description                                                        |
| ---------- | ------------------------ | ---------- | ------------------------------------------------------------------ |
| `href`     | `Href`                   | -          | The destination URL or route. Can be string or route object.       |
| `browser`  | `'in-app' \| 'external'` | `'in-app'` | How external links should open (in-app browser vs system browser). |
| `children` | `ReactNode`              | -          | The content of the link. Can be text or custom components.         |

All other props from `expo-router`'s `Link` component are also supported.

### Href Types

The `href` prop accepts the following formats:

- **Internal routes**: `"/profile"`, `"/settings"`, `{ pathname: "/user", params: { id: "123" } }`
- **External URLs**: `"https://example.com"`, `"http://example.com"`
- **Email links**: `"mailto:user@example.com"`
- **Phone links**: `"tel:+1234567890"`

`href`'s `Href` type comes directly from `expo-router`. For it to type-check
internal routes against your app's actual file-based routes (autocomplete and
compile errors on typos), enable typed routes in `app.json`:

```json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}
```

Without it, `href` still works at runtime — it just widens to `string`,
losing route-name validation.

## Browser Behavior

### In-App Browser (Default)

When `browser="in-app"` (default), external links open in an in-app browser:

- **Mobile**: Uses `expo-web-browser` for a seamless in-app experience
- **Web**: Opens in a new tab

### External Browser

When `browser="external"`, external links open in the system browser:

- **Mobile**: Uses the device's default browser app
- **Web**: Opens in a new tab

## Accessibility

The Link component maintains accessibility features:

- Proper semantic link structure for screen readers
- Keyboard navigation support
- Focus management
- ARIA attributes when using custom children
- Text decoration for visual link indication

## Platform Considerations

### React Native

- Internal navigation uses Expo Router's navigation system
- External links use either `expo-web-browser` or `Linking` API
- Haptic feedback can be added through custom styling

### Web

- Internal navigation uses client-side routing
- External links open in new tabs
- Standard web link behavior and styling apply

## Best Practices

1. **Use descriptive link text** that clearly indicates the destination
2. **Choose appropriate browser behavior** based on user context
3. **Test on both platforms** to ensure consistent behavior
4. **Consider loading states** for external links that might take time to open
5. **Use custom children sparingly** - text links are more accessible
