Link
An Expo Router API that provides Link, Redirect, preview, and zoom transition components.
An Expo Router API that provides components for navigating between routes, including link, redirect, preview, and zoom transitions.
See the Expo Router reference for installation and configuration.
Usage
For more information about navigating between routes, read the navigation guide:
Navigate between pages — Learn how to navigate between pages in Expo Router.
API
import { Link, Redirect } from 'expo-router';Components
Link
Type: React.Element<LinkProps>
Component that renders a link using href to another route. By default, it accepts children and wraps them in a <Text> component.
Uses an anchor tag (<a>) on web and performs a client-side navigation to preserve the state of the website and navigate faster.
- The web-only attributes such as
target,rel, anddownloadare supported and passed to the anchor tag on web. - See 4 for more details.
Example
LinkProps
asChild
Optional • Type: boolean
Used to customize the Link component. It will forward all props to the first child of the Link. Note that the child component must accept onPress or onClick props. The href and role are also passed to the child.
Example
className
Optional • Type: string
On native, this can be used with CSS interop tools like Nativewind. On web, this sets the HTML class directly.
dangerouslySingular
Optional • Type: SingularOptions
When navigating in a Stack, if the target is valid then screens in the history that matches the uniqueness constraint will be removed.
If used with push, the history will be filtered even if no navigation occurs.
dismissTo
Optional • Type: boolean
While in a stack, this will dismiss screens until the provided href is reached. If the href is not found, it will instead replace the current screen with the provided href.
Example
href
Literal type: union
The path of the route to navigate to. It can either be:
- string: A full path like
/profile/settingsor a relative path like../settings. - object: An object with a
pathnameand optionalparams. Thepathnamecan be a full path like/profile/settingsor a relative path like../settings. The params can be an object of key-value pairs.
Example
Acceptable values are: string | HrefObject
onPress
Optional • Type: (event: MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent) => void
This function is called on press. Text intrinsically supports press handling with a default highlight state (which can be disabled with suppressHighlighting).
prefetch
Optional • Type: boolean
Prefetches the route when the component is rendered on a focused screen.
push
Optional • Type: boolean
Always pushes a new route, and never pops or replaces to existing route. You can push the current route multiple times or with new parameters.
Example
ref
Optional • Type: Ref<Text>
relativeToDirectory
Optional • Type: boolean
Relative URL references are either relative to the directory or the document. By default, relative paths are relative to the document.
replace
Optional • Type: boolean
Removes the current route from the history and replace it with the specified URL. This is useful for redirects.
Example
withAnchor
Optional • Type: boolean
Replaces the initial screen with the current route.
Inherited props
- Omit<TextProps, 'href'>
- WebAnchorProps
Redirect
Type: React.Element<RedirectProps>
Redirects to the href as soon as the component is mounted.
Example
import { View, Text } from 'react-native';
import { Redirect } from 'expo-router';
export default function Page() {
const { user } = useAuth();
if (!user) {
return <Redirect href="/login" />;
}
return (
<View>
<Text>Welcome Back!</Text>
</View>
);
}RedirectProps
href
Type: Href
The path of the route to navigate to. It can either be:
- string: A full path like
/profile/settingsor a relative path like../settings. - object: An object with a
pathnameand optionalparams. Thepathnamecan be a full path like/profile/settingsor a relative path like../settings. The params can be an object of key-value pairs.
Example
import { Redirect } from 'expo-router';
export default function RedirectToAbout() {
return (
<Redirect href="/about" />
);
}relativeToDirectory
Optional • Type: boolean
Relative URL references are either relative to the directory or the document. By default, relative paths are relative to the document.
withAnchor
Optional • Type: boolean
Replaces the initial screen with the current route.
Link.AppleZoom
iOS 18+
Type: React.Element<LinkAppleZoomProps>
When this component is used inside a Link, zoom transition will be used when navigating to the link's href.
LinkAppleZoomProps
alignmentRect
iOS 18+
Optional • Type: { height: number, width: number, x: number, y: number }
Defines the rectangle used for the zoom transition's alignment. This rectangle is specified in the zoomed screen's coordinate space.
Inherited props
PropsWithChildren
Link.AppleZoomTarget
iOS 18+
Type: React.Iterable<{ children: ReactNode }>
Defines the target for an Apple zoom transition.
Example
import { Link } from 'expo-router';
export default function Screen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Link.AppleZoomTarget>
<Image source={require('../assets/image.png')} style={{ width: 200, height: 200 }} />
</Link.AppleZoomTarget>
</View>
);
}Link.Menu
Type: React.Element<LinkMenuProps>
Groups context menu actions for a link.
If multiple Link.Menu components are used within a single Link, only the first will be rendered. Only Link.MenuAction and Link.Menu components are allowed as children.
Example
<Link.Menu>
<Link.MenuAction title="Action 1" onPress={() => {}} />
<Link.MenuAction title="Action 2" onPress={() => {}} />
</Link.Menu>LinkMenuProps
children
Optional • Type: ReactNode
destructive
Optional • Type: boolean
If true, the menu item will be displayed as destructive.
See: Apple documentation for more information.
displayAsPalette
Deprecated: Use
paletteprop instead.
Optional • Type: boolean
displayInline
Deprecated: Use
inlineprop instead.
Optional • Type: boolean
elementSize
iOS 16+
Optional • Literal type: string
The preferred size of the menu elements. elementSize property is ignored when palette is used.
See: Apple documentation for more information.
Acceptable values are: 'small' | 'medium' | 'auto' | 'large'
icon
Optional • Type: SFSymbols7_0
Optional SF Symbol displayed alongside the menu item.
image
Optional • Literal type: union
Custom image loaded using useImage() hook from expo-image. Takes priority over icon (SF Symbol) when both are provided.
Example
Acceptable values are: SharedRef<'image', Record<never, never>> | null
inline
Optional • Type: boolean
If true, the menu will be displayed inline. This means that the menu will not be collapsed
See: Apple documentation for more information.
palette
Optional • Type: boolean
If true, the menu will be displayed as a palette.
- This means that the menu will be displayed as one row.
- The
elementSizeproperty is ignored when palette is used, all items will beelementSize="small". - Use
elementSize="medium"instead ofpaletteto display actions with titles horizontally.
Note: Palette menus are only supported in submenus.
See: Apple documentation for more information.
subtitle
Optional • Type: string
An optional subtitle for the submenu. Does not appear on inline menus.
See: Apple documentation for more information.
title
Optional • Type: string
The title of the menu item
Link.MenuAction
Type: React.Element<LinkMenuActionProps>
This component renders a context menu action for a link. It should only be used as a child of Link.Menu or LinkMenu.
LinkMenuActionProps
children
Optional • Type: ReactNode
The title of the menu item.
destructive
Optional • Type: boolean
If true, the menu item will be displayed as destructive.
See: Apple documentation for more information.
disabled
Optional • Type: boolean
If true, the menu item will be disabled and not selectable.
See: Apple documentation for more information.
discoverabilityLabel
Optional • Type: string
An elaborated title that explains the purpose of the action.
hidden
Optional • Type: boolean • Default: false
Whether the menu element should be hidden.
See: Apple documentation for more information.
icon
Optional • Type: SFSymbols7_0
SF Symbol displayed alongside the menu item.
image
Optional • Literal type: union
Custom image loaded using useImage() hook from expo-image. Takes priority over icon (SF Symbol) when both are provided.
Example
Acceptable values are: SharedRef<'image', Record<never, never>> | null
imageRenderingMode
Optional • Literal type: string
Controls how image-based icons are rendered on iOS.
'template': iOS applies tint color to the icon'original': Preserves original icon colors
See: Apple documentation for more information.
Acceptable values are: 'template' | 'original'
isOn
Optional • Type: boolean
If true, the menu item will be displayed as selected.
onPress
Optional • Type: () => void
subtitle
Optional • Type: string
An optional subtitle for the menu item.
See: Apple documentation for more information.
title
Deprecated: Use
childrenprop instead.
Optional • Type: string
The title of the menu item.
unstable_keepPresented
Optional • Type: boolean
If true, the menu will be kept presented after the action is selected.
This is marked as unstable, because when action is selected it will recreate the menu, which will close all opened submenus and reset the scroll position.
See: Apple documentation for more information.
Link.Preview
Type: React.Element<LinkPreviewProps>
A component used to render and customize the link preview.
If Link.Preview is used without any props, it will render a preview of the href passed to the Link.
If multiple Link.Preview components are used within a single Link, only the first one will be rendered.
To customize the preview, you can pass custom content as children.
Example
<Link href="/about">
<Link.Preview>
<Text>Custom Preview Content</Text>
</Link.Preview>
</Link>Example
<Link href="/about">
<Link.Preview />
</Link>LinkPreviewProps
children
Optional • Type: ReactNode
style
Optional • Type: LinkPreviewStyle
Custom styles for the preview container.
Note that some styles may not work, as they are limited or reset by the native view
Link.Trigger
Type: React.Iterable<LinkTriggerProps>
Serves as the trigger for a link. The content inside this component will be rendered as part of the base link.
If multiple Link.Trigger components are used within a single Link, only the first will be rendered.
Example
<Link href="/about">
<Link.Trigger>
Trigger
</Link.Trigger>
</Link>LinkTriggerProps
withAppleZoom
iOS 18+
Optional • Type: boolean
A shorthand for enabling the Apple Zoom Transition on this link trigger.
When set to true, the trigger will be wrapped with Link.AppleZoom. If another Link.AppleZoom is already used inside Link.Trigger, an error will be thrown.
Inherited props
PropsWithChildren
Hooks
useIsPreview()
Hook to determine if the current route is rendered inside a preview.
Returns: boolean
- True if the current route is rendered inside a preview, false otherwise.
usePreventZoomTransitionDismissal(_options)
| Parameter | Type |
|---|---|
_options(optional) | UsePreventZoomTransitionDismissalOptions |
Limits the screen area where interactive dismissal gestures are allowed for zoom transitions.
This hook must be called from the destination screen of a zoom transition (the screen you navigate to, not the source). It restricts where app users can start swipe gestures to dismiss the screen and return to the previous screen.
When a dismissal gesture starts inside the bounds, the screen can be dismissed. When a dismissal gesture starts outside the bounds, dismissal is blocked completely. Undefined coordinates place no restriction on that dimension.
Note: Only one instance of this hook should be used per screen. If multiple instances exist, the last one to render will take effect.
Returns: void
Example
// In your destination screen (e.g., app/image.tsx)
import { usePreventZoomTransitionDismissal } from 'expo-router';
import { useWindowDimensions } from 'react-native';
import { Image } from 'expo-image';
export default function ImageScreen() {
const dimensions = useWindowDimensions();
// Only allow dismissal from the bottom 200px of the screen
usePreventZoomTransitionDismissal({
unstable_dismissalBoundsRect: {
minY: dimensions.height - 200
}
});
return <Image source={...} style={{ flex: 1 }} />;
}Interfaces
DismissalBoundsRect
Defines the screen bounds where interactive dismissal gestures are allowed for zoom transitions.
| Property | Type | Description |
|---|---|---|
| maxX(optional) | number | Maximum X coordinate (right edge) where dismissal gestures are allowed. |
| maxY(optional) | number | Maximum Y coordinate (bottom edge) where dismissal gestures are allowed. |
| minX(optional) | number | Minimum X coordinate (left edge) where dismissal gestures are allowed. |
| minY(optional) | number | Minimum Y coordinate (top edge) where dismissal gestures are allowed. |
UsePreventZoomTransitionDismissalOptions
| Property | Type | Description |
|---|---|---|
| unstable_dismissalBoundsRect(optional) | DismissalBoundsRect | Defines the screen bounds where interactive dismissal gestures are allowed. Each coordinate is optional. Undefined coordinates place no restriction on that dimension. For example, if only minY and maxY are defined, horizontal gestures are unrestricted while vertical gestures must stay within the Y bounds. See: Apple documentation for more information. |
Types
Link previews
Link preview is an iOS-only feature available in SDK 54 and later.
Link preview (also known as "Peek and Pop") is a feature commonly used on iOS to show users a preview popup of the screen for a link. This guide will show you how to add and customize a link preview for your app on iOS.
If you have a link in your app, you can add a link preview to it by replacing the link's content with a Link.Trigger and adding the Link.Preview component to it. This will create a preview of the page that the link points to.
Customizing the link preview
By default, the link preview is rendered as a full-sized page snapshot. There are several ways to customize this behavior.
Custom size
You can use width and height to suggest a preferred preview size. The system will consider these preferences but may override them based on available space or platform behavior.
<Link href="...">
<Link.Trigger>Content</Link.Trigger>
<Link.Preview style={{ width: 300, height: 200 }} />
</Link>The following example shows a custom link preview size on iOS:
Custom preview
If you don't want to show the default preview, you can pass custom content to the Link.Preview component via children. This custom content will replace the default preview of the link target.
export default function Page() {
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
const { width } = useWindowDimensions();
const previewHeight = (width / imageSize.width) * imageSize.height;
return (
<Link href="/about">
<Link.Trigger>About</Link.Trigger>
<Link.Preview style={{ width, height: previewHeight }}>
<Image
onLoad={e => setImageSize(e.nativeEvent.source)}
source={source}
style={{ width: '100%', height: '100%' }}
/>
</Link.Preview>
</Link>
);
}The following example shows a custom link preview on iOS:
Menu
To render a context menu next to the preview, add a Link.Menu with Link.MenuAction children.
<Link href="/about">
<Link.Trigger>About</Link.Trigger>
<Link.Menu>
<Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={handleSharePress} />
<Link.MenuAction title="Block" icon="nosign" destructive onPress={handleBlockPress} />
</Link.Menu>
</Link>The following example shows a custom link preview on iOS:
Icons
You can specify an icon for each menu action using SF Symbols.
<Link href="/about">
<Link.Trigger>About</Link.Trigger>
<Link.Menu>
<Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={handleSharePress} />
<Link.MenuAction title="Block" icon="nosign" onPress={handleBlockPress} />
<Link.MenuAction
title="Follow"
icon="person.crop.circle.badge.plus"
onPress={handleFollowPress}
/>
<Link.MenuAction title="Copy" icon="doc.on.doc" onPress={handleCopyPress} />
</Link.Menu>
</Link>The following example shows a context menu with four elements, each using a different icon on iOS:
Nested menus
You can nest menus by placing a Link.Menu inside another menu:
<Link href="...">
<Link.Trigger>About</Link.Trigger>
<Link.Menu>
<Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={() => {}} />
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction title="Delete" icon="trash" destructive onPress={() => {}} />
</Link.Menu>
</Link.Menu>
</Link>The following example shows a nested context menu on iOS:
More customization options
To explore all available customization options, see the API documentation for Link.MenuAction.
Detecting if component is in preview
If you're building a component that might be rendered inside a preview, you can use the useIsPreview() hook to adjust its behavior accordingly:
function MyComponent() {
// This will be true if component/screen is being rendered inside a preview
const isInsidePreview = useIsPreview();
return isInsidePreview ? <Text>From within preview</Text> : <Text>I am outside of preview</Text>;
}Known limitations
replace not supported
Using link previews with replace mode is currently not supported. Previews can only be used with the default push navigation mode.
JavaScript tabs and slots
When navigating within JavaScript tabs (rather than native tabs) or 0, preview transition animations may appear clunky.
- This is due to React's delayed rendering while the native preview animation begins immediately.
- To prevent this issues use, native tabs and stack navigators.
Missing Link.Trigger
If you render a Link with a preview or context menu, but without a Link.Trigger, an exception will be thrown. The same applies if you place any non-Link.* component directly inside Link when using preview mode.
Multiple Link.Trigger children with asChild prop
When using Link with asChild, you may only specify one child for Link.Trigger. The onPress event will be forwarded to that child only.
Changing href while preview is open
Changing href prop's path dynamically while the preview is open is not supported. You may only modify the query parameters dynamically.
Custom link handling
Expo Router uses an extended version of web standards to navigate through an app.
-
However, native apps do not always conform to server-based routing.
-
This can lead to misalignment when integrating any third-party service.
-
For example, apps can be launched with arbitrary strings or intent objects instead of URLs.
-
There are two common scenarios where you may need to customize a link:
-
App Closed: When the app is not open, incoming deep-link URLs may require rewriting to ensure seamless navigation.
-
App Open: When the app is already open, URL customization may be necessary based on specific business logic or user interactions. This logic can be global for the entire app, or localized to a set of routes.
Setup linking
See the Linking into your app guide for instructions on how to set up and test linking in your app.
Rewrite incoming native deep links
Expo Router will always evaluate a URL with the assumption that the URL targets a specific page within the app. However, in practice, these URLs can vary in nature:
- Unique/Referred URLs from third-party providers: These URLs often follow a specific schema, such as
<my-scheme>://<provider-hostname>/<uuid>, and are generated by external sources to navigate users to designated content within the app. - Stale URLs from previous versions: App users might encounter URLs from older versions of the app, which could lead to outdated or non-existent content. It's crucial to handle such scenarios gracefully to ensure a smooth user experience.
In such scenarios, the URL needs to be rewritten to correctly target a route.
To facilitate this, create a special file called +native-intent.tsx at the top level of your project's src/app directory.
- This file exports a special 0 method designed to handle URL/path processing.
- When invoked, it receives an
optionsobject with two attributes:pathandinitial.
src
app
- +native-intent.tsx
Here's an example the applies practices on how redirectSystemPath is used inside +native-intent.tsx file. Following this example, you can ensure the stability and reliability of your app's URL processing functionality and mitigate the risk of unexpected errors and crashes.
import ThirdPartyService from 'third-party-sdk';
export function redirectSystemPath({ path, initial }) {
try {
if (initial) {
// While the parameter is called `path` there is no guarantee that this is a path or a valid URL
const url = new URL(path, 'myapp://app.home');
// Detection of third-party URLs will change based on the provider
if (url.hostname === '<third-party-provider-hostname>') {
return ThirdPartyService.processReferringUrl(url).catch(() => {
// Something went wrong
return '/unexpected-error';
});
}
return path;
}
return path;
} catch {
// Do not crash inside this function! Instead you should redirect users
// to a custom route to handle unexpected errors, where they are able to report the incident
return '/unexpected-error';
}
}Rewrite URLs
While your app is open, you can react to URL changes within your _layout files using the usePathname() hook. The location of the _layout dictates the scope of the subscription.
- global: Add the logic to your root
_layoutfile - localized: Add a
_layoutfile to an existing directory (or create a new group directory)
import { Slot, Redirect } from 'expo-router';
export default function RootLayout() {
const pathname = usePathname();
if (pathname && !isUserAllowed(pathname)) {
return <Redirect href="/home" />;
}
return <Slot />;
}Using redirectSystemPath
In native apps, an alternative way to rewrite a URL is to handle it within the redirectSystemPath method. This approach can be simpler for some use cases but comes with certain drawbacks:
- Native-only: This method will not work on the web, as
+native-intentis only available in native apps. - Lacks context:
+native-intentis processed outside the context of your app. This means you won't have access to additional logic, such as user authentication status or the current route's state.
Sending navigation events to third-party services
Below is a basic example of how to send navigation events to an external service, such as an analytics or logging service. Consult with your provider for specific instructions.
import ThirdPartyService from 'third-party-sdk';
import { Slot, usePathname } from 'expo-router';
const thirdParty = new ThirdPartyService();
export default function RootLayout() {
const pathname = usePathname();
// Perform the service initiation logic
useEffect(() => {
thirdParty.register();
return () => {
thirdParty.deregister();
};
}, [thirdParty]);
// Send pathname changes to the third party
useEffect(() => {
thirdParty.sendEvent({ pathname });
}, [pathname]);
return <Slot />;
}Universal Links and multiple domains
Expo Router does not require additional configuration for Universal Links and multiple domains.
- All URLs provided to your App will be evaluated.
- To customize the URL scheme(s) for your app, your should customize the 0 value in your app config.
legacy_subscribe
legacy_subscribeis in alpha and is available in SDK 52.
If you're using a third-party provider that doesn't support Expo Router but does support React Navigation via the Linking.subscribe function for existing projects, you can use legacy_subscribe as an alternative API.
Using this API is not recommended for new projects or integrations. Its usage is incompatible with server-side routing and static rendering, and can be challenging to manage while offline or in a low network connectivity environment.
Redirects
You can redirect a request to a different URL based on some in-app criteria. Expo Router supports a number of different redirection patterns.
Using Redirect component
You can immediately redirect from a particular screen by using the Redirect component:
import { View, Text } from 'react-native';
import { Redirect } from 'expo-router';
export default function Page() {
const { user } = useAuth();
if (!user) {
return <Redirect href="/login" />;
}
return (
<View>
<Text>Welcome Back!</Text>
</View>
);
}Using useRouter hook
You can also redirect imperatively with the useRouter hook:
import { Text } from 'react-native';
import { useRouter, useFocusEffect } from 'expo-router';
function MyScreen() {
const router = useRouter();
useFocusEffect(() => {
// Call the replace method to redirect to a new route without adding to the history.
// We do this in a useFocusEffect to ensure the redirect happens every time the screen
// is focused.
router.replace('/profile/settings');
});
return <Text>My Screen</Text>;
}Sources
The original Expo docs for this page:
- Router Link (
source-of-truth/versions/v57.0.0/sdk/router/link.md) - Link preview (
source-of-truth/router/reference/link-preview.md) - Customizing links (
source-of-truth/router/advanced/native-intent.md) - Redirects (
source-of-truth/router/reference/redirects.md)