useFonts hook from expo-font. */ import { useFonts } from 'expo-font'; /* @end */
/* @info Import SplashScreen so that when the fonts are not loaded, we can continue to show SplashScreen. */ import * as SplashScreen from 'expo-splash-screen'; /* @end */
import {useEffect} from 'react';
@@ -321,9 +321,9 @@ The [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) library provides
Inter_900Black and useFonts hook from @expo-google-fonts/inter*/
import { Inter_900Black, useFonts } from '@expo-google-fonts/inter';
@@ -419,7 +419,7 @@ A platform's default font is usually easy-to-read. However, don't be surprised w
When the icons from `@expo/vector-icons` library load for the first time, they appear as invisible icons in your app. Once they load, they're cached for all the app's subsequent usage. To avoid showing invisible icons on your app's first load, preload during the initial loading screen with [`useFonts`](/versions/latest/sdk/font/#usefontsmap). For example:
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { useFonts } from 'expo-font';
import Ionicons from '@expo/vector-icons/Ionicons';
diff --git a/docs/pages/develop/user-interface/safe-areas.mdx b/docs/pages/develop/user-interface/safe-areas.mdx
index 20ff997f62a6ca..b825527cd3ba88 100644
--- a/docs/pages/develop/user-interface/safe-areas.mdx
+++ b/docs/pages/develop/user-interface/safe-areas.mdx
@@ -39,7 +39,7 @@ You can skip installing `react-native-safe-area-context` if you have created a p
You can directly use [`SafeAreaView`](https://appandflow.github.io/react-native-safe-area-context/api/safe-area-view) to wrap the content of your screen's component. It is a regular `router object which is used to navigate imperatively. */ router /* @end */} from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';
@@ -168,7 +168,7 @@ const styles = StyleSheet.create({
By default on iOS, the modal has a dark background which hides the status bar. To change the status bar appearance, you can use the `Platform` API to check if the current platform is iOS and then use the [`StatusBar`](/versions/latest/sdk/status-bar/) component to change the appearance inside the **modal.tsx** file.
{/* prettier-ignore */}
-```tsx app/modal.tsx|collapseHeight=250
+```tsx src/app/modal.tsx|collapseHeight=250
import { StyleSheet, Text, View, Platform } from 'react-native';
import { StatusBar } from 'expo-status-bar';
@@ -216,7 +216,7 @@ Form sheet presents a modal as a bottom sheet that app users can drag between di
To use form sheet, set the `presentation` option to `formSheet` on your modal screen:
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
@@ -246,7 +246,7 @@ Detents define the heights where the sheet can rest. Use `sheetAllowedDetents` t
> **info** Android supports a maximum of 3 detents. iOS accepts any number of detents.
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
@@ -279,7 +279,7 @@ export default function Layout() {
| `sheetCornerRadius` | `number` | Corner radius of the sheet in pixels. |
| `sheetLargestUndimmedDetentIndex` | `number \| 'none' \| 'last'` | Largest detent index that keeps the background undimmed. |
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
@@ -314,7 +314,7 @@ export default function Layout() {
You can add a footer to the sheet that stays visible at all detent positions using a React component:
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { Stack } from 'expo-router';
import { View, Button } from 'react-native';
@@ -347,7 +347,7 @@ export default function Layout() {
When using numeric detents, your modal content can use `flex: 1` to fill the available space within the sheet:
-```tsx app/modal.tsx
+```tsx src/app/modal.tsx
import { StyleSheet, Text, View } from 'react-native';
export default function Modal() {
diff --git a/docs/pages/router/advanced/native-intent.mdx b/docs/pages/router/advanced/native-intent.mdx
index fe7dc16456c85d..5dcba5e5d0ebde 100644
--- a/docs/pages/router/advanced/native-intent.mdx
+++ b/docs/pages/router/advanced/native-intent.mdx
@@ -24,13 +24,13 @@ Expo Router will always evaluate a URL with the assumption that the URL targets
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 **app** directory. This file exports a special [`redirectSystemPath`](/versions/latest/sdk/router/#nativeintent) method designed to handle URL/path processing. When invoked, it receives an `options` object with two attributes: `path` and `initial`.
+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 [`redirectSystemPath`](/versions/latest/sdk/router/#nativeintent) method designed to handle URL/path processing. When invoked, it receives an `options` object with two attributes: `path` and `initial`.
-useRouter from Expo Router. */
import { useRouter } from 'expo-router';
@@ -377,7 +377,7 @@ Dismisses screens in the current `useRouter from Expo Router. */
import { useRouter } from 'expo-router';
@@ -411,7 +411,7 @@ To return to the first screen in the closest stack. This is similar to [`popToTo
For example, the `home` route is the first screen, and the `settings` is the last. To go from `settings` to `home` route you'll have to go back to `details`. However, using the `dismissAll` action, you can go from `settings` to `home` and dismiss any screen in between.
{/* prettier-ignore */}
-```tsx app/settings.tsx
+```tsx src/app/settings.tsx
import { Button, View, Text } from 'react-native';
/* @info Import useRouter from Expo Router. */
import { useRouter } from 'expo-router';
@@ -443,7 +443,7 @@ export default function Settings() {
To check if it is possible to dismiss the current screen. Returns `true` if the router is within a stack with more than one screen in the stack's history.
{/* prettier-ignore */}
-```tsx app/settings.tsx|collapseHeight=410
+```tsx src/app/settings.tsx|collapseHeight=410
import { Button, View } from 'react-native';
/* @info Import useRouter from Expo Router. */
import { useRouter } from 'expo-router';
@@ -506,7 +506,7 @@ export const JsStack = withLayoutContext<
After defining the `JsStack` component, you can use it in your app:
{/* prettier-ignore */}
-```tsx app/_layout.tsx
+```tsx src/app/_layout.tsx
import { JsStack } from '../layouts/js-stack';
export default function Layout() {
@@ -559,7 +559,7 @@ When using `headerLargeTitle: true` (or `unstable_settings can be set in any stack's _layout.tsx file. It is used to define the initial route name for the stack, which ensures that users have a consistent starting point, especially when deep linking. */
@@ -495,7 +495,7 @@ The above example sets the `index` screen as the [`initialRouteName`](/router/ad
Style the overlay and modal content in **modal.tsx** as shown below:
{/* prettier-ignore */}
-```tsx app/modal.tsx|collapseHeight=250
+```tsx src/app/modal.tsx|collapseHeight=250
import { Link } from 'expo-router';
import { Pressable, StyleSheet, Text } from 'react-native';
import Animated, { FadeIn, SlideInDown } from 'react-native-reanimated';
diff --git a/docs/pages/router/advanced/zoom-transition.mdx b/docs/pages/router/advanced/zoom-transition.mdx
index dedfe59a24bbc4..15420ec05c1da5 100644
--- a/docs/pages/router/advanced/zoom-transition.mdx
+++ b/docs/pages/router/advanced/zoom-transition.mdx
@@ -24,7 +24,7 @@ To implement zoom transitions, you need to use the `Link.AppleZoom` component to
To activate zoom transition for a link, wrap the source (`Image`) element with `Link.AppleZoom` in your screen:
-```tsx app/index.tsx
+```tsx src/app/index.tsx
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { Link } from 'expo-router';
import { Image } from 'expo-image';
@@ -49,7 +49,7 @@ export default function HomeScreen() {
In the destination screen, define the `Image` component:
-```tsx app/image.tsx
+```tsx src/app/image.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
@@ -79,7 +79,7 @@ The `Link.AppleZoom` component wraps the element you want to zoom from. It is us
You can specify the alignment of the zoomed element on the destination screen by using `Link.AppleZoomTarget` element.
-```tsx app/image.tsx
+```tsx src/app/image.tsx
export default function ImageScreen() {
return (
initialRouteName ensures that direct links deep into the stack still push the index route onto the stack first. */
@@ -65,11 +65,11 @@ export default function FeedLayout() {
}
```
-Now, within the **app/(tabs)/feed** directory, you can have `Link` components that point to different posts (for example, `/feed/123`). Those links will push the `feed/[postId]` route onto the stack, leaving the tab navigator visible.
+Now, within the **src/app/(tabs)/feed** directory, you can have `Link` components that point to different posts (for example, `/feed/123`). Those links will push the `feed/[postId]` route onto the stack, leaving the tab navigator visible.
You can also navigate from any other tab to a post in the feed tab with the same URL. Use `withAnchor` in conjunction with `initialRouteName` to ensure that the `feed/index` route is always the first screen in the stack:
-```tsx app/(tabs)/feed/index.tsx
+```tsx src/app/(tabs)/feed/index.tsx
Go to post
@@ -84,23 +84,42 @@ You can also nest tabs inside of an outer stack navigator. That is often more us
Icon={BookOpen02Icon}
/>
+## Different tabs per platform: platform-specific tabs
+
+When building a cross-platform app, you may want to use [native tabs](/router/advanced/native-tabs/) on Android and iOS for a platform-native look and feel, while using [custom tabs](/router/advanced/custom-tabs/) on web for full control over styling. You can achieve this using [platform-specific file extensions](/router/advanced/platform-specific-modules/).
+
+Link React component from expo-router. */
import { Link } from 'expo-router';
@@ -132,12 +132,12 @@ Dynamic routes can be linked to with their full URL, or by passing a `params` ob
Consider the following file structure:
-Link React component and router to navigate imperatively from expo-router. */
import { Link, router } from 'expo-router';
/* @end */
@@ -311,9 +311,9 @@ Assuming your `scheme` is `myapp`, here are some examples of how you would link
user is a route parameter
>,
@@ -275,9 +275,9 @@ Route parameters are used to match a route, while search parameters are used to
]}
/>
-When the `app/[user]` route is matched, the `user` parameter is passed to the component and never a nullish value. Both search and route parameters can be used together and are accessible with the `useLocalSearchParams` and `useGlobalSearchParams` hooks:
+When the `src/app/[user]` route is matched, the `user` parameter is passed to the component and never a nullish value. Both search and route parameters can be used together and are accessible with the `useLocalSearchParams` and `useGlobalSearchParams` hooks:
-```tsx app/[user].tsx
+```tsx src/app/[user].tsx
import { useLocalSearchParams } from 'expo-router';
export default function User() {
@@ -300,7 +300,7 @@ export default function User() {
Whenever a route parameter is changed, the component will re-mount.
-```tsx app/[user].tsx
+```tsx src/app/[user].tsx
import { Text } from 'react-native';
import { router, useLocalSearchParams, Link } from 'expo-router';
@@ -324,7 +324,7 @@ TODO: REMOVE COMMENT WHEN https://github.com/expo/expo/pull/30268 IS PUBLISHED
URL parameters that are present multiple times will be grouped together as an array.
-```tsx app/hash.tsx
+```tsx src/app/hash.tsx
import { router, useLocalSearchParams } from 'expo-router';
export default function Route() {
@@ -341,7 +341,7 @@ export default function Route() {
The URL [hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) is a string that follows the `#` symbol in a URL. It is commonly used on websites to link to a specific section of a page, but it can also be used to store data. Expo Router treats the hash as a special search parameter using the name `#`. It can be accessed and modified using the same hooks and APIs from [search parameters](#local-versus-global-search-parameters).
{/* prettier-ignore */}
-```tsx app/hash.tsx
+```tsx src/app/hash.tsx
import { Text } from 'react-native';
import { router, useLocalSearchParams, Link } from 'expo-router';
diff --git a/docs/pages/router/web/api-routes.mdx b/docs/pages/router/web/api-routes.mdx
index c8b0471aab210d..cee602d44d7430 100644
--- a/docs/pages/router/web/api-routes.mdx
+++ b/docs/pages/router/web/api-routes.mdx
@@ -13,9 +13,9 @@ import { Step } from '~/ui/components/Step';
import { Tab, Tabs } from '~/ui/components/Tabs';
import { VideoBoxLink } from '~/ui/components/VideoBoxLink';
-Expo Router enables you to write secure server code for all platforms, right in your **app** directory.
+Expo Router enables you to write secure server code for all platforms, right in your **src/app** directory.
-```ts app/hello+api.ts
+```ts src/app/hello+api.ts
export function GET(request: Request) {
return Response.json({ hello: 'world' });
}
@@ -34,7 +34,7 @@ API Routes are functions that are executed on a server when a route is matched.
In Expo, API Routes are defined by creating files in the **app** directory with the `+api.ts` extension. For example, the following API route is executed when the route `/hello` is matched.
-generateStaticParams will be invoked once for every entry in the array. */
return [{ id: 'one' }, { id: 'two' }];
@@ -107,7 +107,7 @@ export async function generateStaticParams(): PromiseHello World
- > - ); -} -``` - -```css App.module.css -.text { - color: red; -} -``` - -- On web, all CSS values are available. CSS is not processed or auto-prefixed like it is with the React Native Web `StyleSheet` API. You can use `postcss.config.js` to autoprefix your CSS. -- CSS Modules use [lightningcss](https://github.com/parcel-bundler/lightningcss) under the hood, check [the issues](https://github.com/parcel-bundler/lightningcss/issues) for unsupported features. - -### PostCSS - -> Changing the Post CSS or `browserslist` config will require you to clear the Metro cache: `npx expo start --clear` | `npx expo export --clear`. - -[PostCSS](https://github.com/postcss/postcss) can be customized by adding a `postcss.config.json` file to the root of your project. This file should export a function that returns a PostCSS configuration object. For example: - -```json postcss.config.json -{ - "plugins": { - "autoprefixer": {} - } -} -``` - -Both `postcss.config.json` and `postcss.config.js` are supported, but `postcss.config.json` enables better caching. - -### SASS - -Expo Metro has _partial_ support for SCSS/SASS. - -To setup, install the `sass` package in your project: - -npx @react-native-community/cli@latest init to Expo SDK packages.
- >
- }
- Icon={BookOpen02Icon}
-/>
-
-@expo/config-plugins raw functions>}>
-
-When using config plugins with raw functions, it's essential to be aware of certain limitations, particularly in the context of fingerprinting. The library makes a best effort to generate fingerprints for changes made through config plugins; however, raw functions pose specific challenges. Raw functions are not serializable as fingerprints, which means they cannot be directly used for generating unique hashes.
-
-To work around this limitation, the library employs one of the following strategies to create serializable fingerprints for raw functions:
-
-1. Using `Function.name`: The library utilizes the `Function.name` property if available for named raw functions. This property provides a recognizable name for the function, which can be used as a fingerprint property.
-
-2. Using `withAnonymous`: For anonymous raw functions without a `Function.name`, the library resorts to using `withAnonymous` as the fingerprint property. This is a generic identifier for anonymous functions.
-
-Here's an example to illustrate a case in which the library will use [`withMyPlugin`, `withAnonymous`] as plugin properties for fingerprint hashing:
-
-{/* prettier-ignore */}
-```js app.config.js
-const { withInfoPlist } = require('expo/config-plugins');
-
-const withMyPlugin = (config) => {
- return withInfoPlist(config, (config) => {
- config.modResults.NSLocationWhenInUseUsageDescription = 'Allow $(PRODUCT_NAME) to use your location';
- return config;
- });
-};
-
-export default ({ config }) => {
- config.plugins ||= [];
- config.plugins.push(withMyPlugin);
- config.plugins.push((config) => config);
- return config;
-};
-```
-
-It's important to note that due to this design, if you make changes to the implementation of raw config plugins functions, such as altering the **Info.plist** value within `withMyPlugin`, the fingerprint will still generate the same hash value. To ensure unique fingerprints when modifying config plugins implementations, consider the following options:
-
-- Avoid Anonymous Functions: Avoid using anonymous raw config plugins functions. Instead, use named functions whenever possible, and ensure that their names remain consistent as long as the implementation changes.
-
-- Use Local config plugins: Alternatively, you can create local config plugins as separate modules, each with its own export. This approach allows you to specify a different function name when making changes to the config plugins implementations.
-
-Here's an example of using a local config plugin:
-
-```js ./plugins/withMyPlugin.js
-const { withInfoPlist } = require('expo/config-plugins');
-
-const withMyPlugin = config => {
- return withInfoPlist(config, config => {
- config.modResults.NSLocationWhenInUseUsageDescription =
- 'Allow $(PRODUCT_NAME) to use your location';
- return config;
- });
-};
-
-module.exports = withMyPlugin;
-```
-
-{/* prettier-ignore */}
-```json app.json
-{
- "expo": {
- /* @hide ... */ /* @end */
- "plugins": "./plugins/withMyPlugin"
- }
-}
-```
-
-By following these guidelines, you can effectively manage changes to config plugins and ensure that fingerprinting remains consistent and reliable.
-
-
-
-
-`;
-
-export default function App() {
- const [selectedPrinter, setSelectedPrinter] = useState();
-
- const print = async () => {
- // On iOS/android prints the given html. On web prints the HTML from the current page.
- /* @info */ await Print.printAsync({
- html,
- printerUrl: selectedPrinter?.url, // iOS only
- }); /* @end */
- };
-
- const printToFile = async () => {
- // On iOS/android prints the given html. On web prints the HTML from the current page.
- /* @info */ const { uri } = await Print.printToFileAsync({ html }); /* @end */
- console.log('File has been saved to:', uri);
- await shareAsync(uri, { UTI: '.pdf', mimeType: 'application/pdf' });
- };
-
- const selectPrinter = async () => {
- /* @info */ const printer = await Print.selectPrinterAsync(); // iOS only
- /* @end */
- setSelectedPrinter(printer);
- };
-
- return (
- Edge[]
-
- {'\u2002ā¢\u2002Default: '}
- ["top", "right", "bottom", "left"]
-boolean
- {'\u2002ā¢\u2002Default: '}
- true
-