Skip to content

alouette

A modern, customizable design system built on top of NativeWind v5 with configurable defaults

alouette-external-links

161 linesSource

Send the user to a URL outside the app. ExternalLink is the primitive over expo-web-browser and Linking; ExternalLinkButton is the call-to-action shaped call site and ExternalLinkText the inline one inside a sentence. How the link opens is decided per platform — an in-app browser or the system one on native, a new tab or the same one on web — and the two wrappers already pick the usual answer. Load when linking out to external URLs from alouette UI.

This skill builds on alouette-actions. Read it first for button props.

alouette + expo-web-browser — External links

ExternalLink centralizes outbound-URL behavior per platform: an in-app browser sheet (expo-web-browser) or Linking on native, and a new tab or same tab on web. Two components apply it for you — ExternalLinkButton for a call to action, ExternalLinkText for a link inside a text flow.

ExternalOpenLinkBehavior (exported from alouette): { native: "linking" | "webBrowser"; web: "targetBlank" | "targetSelf" }. Both wrappers default it to { native: "webBrowser", web: "targetBlank" }.

Setup

tsx
import { ExternalLinkButton, ExternalLinkText } from "alouette";

<ExternalLinkButton href="https://example.com" text="Open docs" />
<ExternalLinkText href="https://example.com" text="the documentation" />;

Core Patterns

ExternalLinkButton takes every Button prop (variant, size, accent, icon, state, …) and reads as an action. ExternalLinkText is the lightweight one: bold underlined text preceded by an affordance icon (the external-link arrow, overridable via icon, dropped with icon={null}), size "sm" | "md", an accent, and a disabled state — on web the text and icon tint through hover/active from the interactive tokens, and the row carries the focus-visible outline.

tsx
<Paragraph>
  Read <ExternalLinkText size="sm" href={docsUrl} text="the guide" /> first.
</Paragraph>

Inside a Text or Paragraph it renders as a nested Text, so it wraps with the sentence and keeps its baseline on native as on web.

A disabled link drops its href on both platforms, because a disabled Pressable never sees the press that would cancel the navigation. On web both wrappers go through ExternalLink, which renders a real anchor with target="_blank" + rel="noopener noreferrer" (unless web: "targetSelf"), so middle-click and copy-link work.

Wrap any component with explicit per-platform behavior

ExternalLink takes the target component via as and forwards remaining props.

tsx
import { ExternalLink, Button } from "alouette";

<ExternalLink
  as={Button}
  href="https://example.com"
  openLinkBehavior={{ native: "webBrowser", web: "targetBlank" }}
  text="Open"
/>;
  • native: "webBrowser" opens an in-app browser sheet themed with alouette colors.
  • native: "linking" hands off to the OS browser via Linking.openURL.
  • web: "targetBlank" opens a new tab; "targetSelf" navigates in place.

Common Mistakes

MEDIUM Calling Linking.openURL directly

Wrong:

tsx
import { Linking } from "react-native";
<Pressable onPress={() => Linking.openURL(href)} />;

Correct:

tsx
<ExternalLink
  as={Button}
  href={href}
  openLinkBehavior={{ native: "webBrowser", web: "targetBlank" }}
  text="Open"
/>

ExternalLink handles the in-app themed browser sheet on native and the correct target behavior on web; calling Linking directly loses both.

Source: packages/alouette/src/expo/ExternalLink.tsx

MEDIUM Omitting openLinkBehavior

Wrong:

tsx
<ExternalLink as={Button} href={href} text="Open" />

Correct:

tsx
<ExternalLink
  as={Button}
  href={href}
  openLinkBehavior={{ native: "linking", web: "targetSelf" }}
  text="Open"
/>

openLinkBehavior is required on ExternalLink itself and selects the native and web strategies; without it the link can't decide how to open and throws on an unsupported branch. (ExternalLinkButton and ExternalLinkText default it for you — use them for the simple case.)

Source: packages/alouette/src/expo/ExternalLink.tsx; expo/ExternalLink.shared.ts

MEDIUM A pressable Text instead of ExternalLinkText

Wrong:

tsx
<Pressable onPress={() => openUrl(href)}>
  <Text className="underline text-accent">the documentation</Text>
</Pressable>

Correct:

tsx
<ExternalLinkText href={href} text="the documentation" />

The hand-rolled version has no role="link", no affordance icon, no hover/active/focus tint from the interactive tokens, and on web no real anchor — so the link cannot be middle-clicked or copied.

Source: packages/alouette/src/ui/actions/ExternalLinkText.tsx