├── .gitignore ├── README.md ├── app.json ├── app ├── (tabs) │ ├── _layout.tsx │ ├── explore.tsx │ └── index.tsx ├── +html.tsx ├── +not-found.tsx └── _layout.tsx ├── assets ├── fonts │ └── SpaceMono-Regular.ttf └── images │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ ├── partial-react-logo.png │ ├── react-logo.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ └── splash.png ├── babel.config.js ├── bun.lockb ├── components ├── Collapsible.tsx ├── ExternalLink.tsx ├── HelloWave.tsx ├── ParallaxScrollView.tsx ├── ThemedText.tsx ├── ThemedView.tsx ├── __tests__ │ ├── ThemedText-test.tsx │ └── __snapshots__ │ │ └── ThemedText-test.tsx.snap ├── bottom-tabs.tsx └── navigation │ └── TabBarIcon.tsx ├── constants └── Colors.ts ├── hooks ├── useColorScheme.ts ├── useColorScheme.web.ts └── useThemeColor.ts ├── package.json ├── scripts └── reset-project.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 17 | # The following patterns were generated by expo-cli 18 | 19 | expo-env.d.ts 20 | # @end expo-cli 21 | 22 | /ios 23 | /android -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expo Router + Native bottom tabs 2 | 3 | > This project requires a custom dev client and does not run in Expo Go. 4 | 5 | This is an example of using [`react-native-bottom-tabs`](https://github.com/okwasniewski/react-native-bottom-tabs) with Expo Router to use Apple's built-in native bottom tabs component. The minimum iOS version had to be bumped to 14.0 (which will be the default in SDK 52). 6 | 7 | First, create a custom layout adapter for the native bottom tabs: 8 | 9 | ```js 10 | import { withLayoutContext } from "expo-router"; 11 | import { createNativeBottomTabNavigator } from "react-native-bottom-tabs/react-navigation"; 12 | 13 | export const Tabs = withLayoutContext( 14 | createNativeBottomTabNavigator().Navigator 15 | ); 16 | ``` 17 | 18 | Then, use the `Tabs` component in your app: 19 | 20 | ```js 21 | import { Tabs } from "@/components/bottom-tabs"; 22 | 23 | export default function TabLayout() { 24 | return ( 25 | 26 | ({ sfSymbol: "house" }), 31 | }} 32 | /> 33 | ({ sfSymbol: "person" }), 38 | }} 39 | /> 40 | 41 | ); 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "oct8-tabs", 4 | "slug": "oct8-tabs", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true, 17 | "bundleIdentifier": "com.bacon.oct8-tabs" 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/images/adaptive-icon.png", 22 | "backgroundColor": "#ffffff" 23 | } 24 | }, 25 | "web": { 26 | "bundler": "metro", 27 | "output": "static", 28 | "favicon": "./assets/images/favicon.png" 29 | }, 30 | "plugins": [ 31 | "expo-router", 32 | [ 33 | "expo-build-properties", 34 | { 35 | "ios": { 36 | "deploymentTarget": "14.0" 37 | } 38 | } 39 | ] 40 | ], 41 | "experiments": { 42 | "typedRoutes": true 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/(tabs)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "@/components/bottom-tabs"; 2 | 3 | export default function TabLayout() { 4 | return ( 5 | 6 | ({ sfSymbol: "house" }), 11 | }} 12 | /> 13 | ({ sfSymbol: "person" }), 18 | }} 19 | /> 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/(tabs)/explore.tsx: -------------------------------------------------------------------------------- 1 | import Ionicons from '@expo/vector-icons/Ionicons'; 2 | import { StyleSheet, Image, Platform } from 'react-native'; 3 | 4 | import { Collapsible } from '@/components/Collapsible'; 5 | import { ExternalLink } from '@/components/ExternalLink'; 6 | import ParallaxScrollView from '@/components/ParallaxScrollView'; 7 | import { ThemedText } from '@/components/ThemedText'; 8 | import { ThemedView } from '@/components/ThemedView'; 9 | 10 | export default function TabTwoScreen() { 11 | return ( 12 | }> 15 | 16 | Explore 17 | 18 | This app includes example code to help you get started. 19 | 20 | 21 | This app has two screens:{' '} 22 | app/(tabs)/index.tsx and{' '} 23 | app/(tabs)/explore.tsx 24 | 25 | 26 | The layout file in app/(tabs)/_layout.tsx{' '} 27 | sets up the tab navigator. 28 | 29 | 30 | Learn more 31 | 32 | 33 | 34 | 35 | You can open this project on Android, iOS, and the web. To open the web version, press{' '} 36 | w in the terminal running this project. 37 | 38 | 39 | 40 | 41 | For static images, you can use the @2x and{' '} 42 | @3x suffixes to provide files for 43 | different screen densities 44 | 45 | 46 | 47 | Learn more 48 | 49 | 50 | 51 | 52 | Open app/_layout.tsx to see how to load{' '} 53 | 54 | custom fonts such as this one. 55 | 56 | 57 | 58 | Learn more 59 | 60 | 61 | 62 | 63 | This template has light and dark mode support. The{' '} 64 | useColorScheme() hook lets you inspect 65 | what the user's current color scheme is, and so you can adjust UI colors accordingly. 66 | 67 | 68 | Learn more 69 | 70 | 71 | 72 | 73 | This template includes an example of an animated component. The{' '} 74 | components/HelloWave.tsx component uses 75 | the powerful react-native-reanimated library 76 | to create a waving hand animation. 77 | 78 | {Platform.select({ 79 | ios: ( 80 | 81 | The components/ParallaxScrollView.tsx{' '} 82 | component provides a parallax effect for the header image. 83 | 84 | ), 85 | })} 86 | 87 | 88 | ); 89 | } 90 | 91 | const styles = StyleSheet.create({ 92 | headerImage: { 93 | color: '#808080', 94 | bottom: -90, 95 | left: -35, 96 | position: 'absolute', 97 | }, 98 | titleContainer: { 99 | flexDirection: 'row', 100 | gap: 8, 101 | }, 102 | }); 103 | -------------------------------------------------------------------------------- /app/(tabs)/index.tsx: -------------------------------------------------------------------------------- 1 | import { Image, StyleSheet, Platform } from 'react-native'; 2 | 3 | import { HelloWave } from '@/components/HelloWave'; 4 | import ParallaxScrollView from '@/components/ParallaxScrollView'; 5 | import { ThemedText } from '@/components/ThemedText'; 6 | import { ThemedView } from '@/components/ThemedView'; 7 | 8 | export default function HomeScreen() { 9 | return ( 10 | 17 | }> 18 | 19 | Welcome! 20 | 21 | 22 | 23 | Step 1: Try it 24 | 25 | Edit app/(tabs)/index.tsx to see changes. 26 | Press{' '} 27 | 28 | {Platform.select({ ios: 'cmd + d', android: 'cmd + m' })} 29 | {' '} 30 | to open developer tools. 31 | 32 | 33 | 34 | Step 2: Explore 35 | 36 | Tap the Explore tab to learn more about what's included in this starter app. 37 | 38 | 39 | 40 | Step 3: Get a fresh start 41 | 42 | When you're ready, run{' '} 43 | npm run reset-project to get a fresh{' '} 44 | app directory. This will move the current{' '} 45 | app to{' '} 46 | app-example. 47 | 48 | 49 | 50 | ); 51 | } 52 | 53 | const styles = StyleSheet.create({ 54 | titleContainer: { 55 | flexDirection: 'row', 56 | alignItems: 'center', 57 | gap: 8, 58 | }, 59 | stepContainer: { 60 | gap: 8, 61 | marginBottom: 8, 62 | }, 63 | reactLogo: { 64 | height: 178, 65 | width: 290, 66 | bottom: 0, 67 | left: 0, 68 | position: 'absolute', 69 | }, 70 | }); 71 | -------------------------------------------------------------------------------- /app/+html.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollViewStyleReset } from 'expo-router/html'; 2 | import { type PropsWithChildren } from 'react'; 3 | 4 | /** 5 | * This file is web-only and used to configure the root HTML for every web page during static rendering. 6 | * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. 7 | */ 8 | export default function Root({ children }: PropsWithChildren) { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | {/* 17 | Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 18 | However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 19 | */} 20 | 21 | 22 | {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 23 |