# Native Tabs Always prefer NativeTabs from 'expo-router/unstable-native-tabs' for the best iOS experience. **Requires SDK 54+** ## Basic Usage ```tsx import { NativeTabs, Icon, Label, Badge, } from "expo-router/unstable-native-tabs"; export default function TabLayout() { return ( 9+ ); } ``` ## Rules - You must include a trigger for each tab - The `NativeTabs.Trigger` 'name' must match the route name, including parentheses (e.g. ``) - Prefer search tab to be last in the list so it can combine with the search bar - Use the 'role' prop for common tab types ## Platform Features Native Tabs use platform-specific tab bar implementations: - **iOS 26+**: Liquid glass effects with system-native appearance - **Android**: Material 3 bottom navigation - Better performance and native feel ## Icon Component ```tsx // SF Symbol only (iOS) // With Android drawable // Custom image source // State variants (default/selected) ``` ## Label Component ```tsx // Basic label // Hidden label (icon only) ``` ## Badge Component ```tsx // Numeric badge 9+ // Dot indicator (empty badge) ``` ## iOS 26 Features ### Liquid Glass Tab Bar The tab bar automatically adopts liquid glass appearance on iOS 26+. ### Minimize on Scroll ```tsx ``` ### Search Tab Add a dedicated search tab that integrates with the tab bar search field: ```tsx ``` **Note**: Place search tab last for best UX. ### Role Prop Use semantic roles for special tab types: ```tsx ``` Available roles: `search` | `more` | `favorites` | `bookmarks` | `contacts` | `downloads` | `featured` | `history` | `mostRecent` | `mostViewed` | `recents` | `topRated` ## Customization ### Tint Color ```tsx ``` ### Dynamic Colors (iOS) Use DynamicColorIOS for colors that adapt to liquid glass: ```tsx import { DynamicColorIOS, Platform } from 'react-native'; const adaptiveBlue = Platform.select({ ios: DynamicColorIOS({ light: '#007AFF', dark: '#0A84FF' }), default: '#007AFF', }); ``` ## Conditional Tabs Hide tabs conditionally: ```tsx ``` ## Behavior Options ```tsx ``` ## Using Vector Icons If you must use @expo/vector-icons instead of SF Symbols: ```tsx import { VectorIcon } from "expo-router/unstable-native-tabs"; import Ionicons from "@expo/vector-icons/Ionicons"; ; ``` **Prefer SF Symbols over vector icons for native feel on Apple platforms.** ## Structure with Stacks Native tabs don't render headers. Nest Stacks inside each tab for navigation headers: ```tsx // app/(tabs)/_layout.tsx import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs"; export default function TabLayout() { return ( ); } // app/(tabs)/(home)/_layout.tsx import Stack from "expo-router/stack"; export default function HomeStack() { return ( ); } ``` ## Migration from JS Tabs ### Before (JS Tabs) ```tsx import { Tabs } from "expo-router"; export default function TabLayout() { return ( ( ), }} /> , }} /> ); } ``` ### After (Native Tabs) ```tsx import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs"; export default function TabLayout() { return ( ); } ``` ### Key Differences | JS Tabs | Native Tabs | | -------------------------- | ------------------------- | | `` | `` | | `options={{ title }}` | `` | | `options={{ tabBarIcon }}` | `` | | Props-based API | React component-based API | | `tabBarBadge` option | `` component | ### Migration Steps 1. **Change imports** ```tsx // Remove import { Tabs } from "expo-router"; // Add import { NativeTabs, Icon, Label, Badge, } from "expo-router/unstable-native-tabs"; ``` 2. **Replace Tabs with NativeTabs** ```tsx // Before // After ``` 3. **Convert each Screen to Trigger** ```tsx // Before , tabBarBadge: 3, }} /> // After 3 ``` 4. **Move headers to nested Stack** - Native tabs don't render headers ``` app/ (tabs)/ _layout.tsx <- NativeTabs (home)/ _layout.tsx <- Stack with headers index.tsx (settings)/ _layout.tsx <- Stack with headers index.tsx ``` ## Limitations - **Android**: Maximum 5 tabs (Material Design constraint) - **Nesting**: Native tabs cannot nest inside other native tabs - **Tab bar height**: Cannot be measured programmatically - **FlatList transparency**: Use `disableTransparentOnScrollEdge` to fix issues ## Keyboard Handling (Android) Configure in app.json: ```json { "expo": { "android": { "softwareKeyboardLayoutMode": "resize" } } } ``` ## Common Issues 1. **Icons not showing on Android**: Add `drawable` prop or use `VectorIcon` 2. **Headers missing**: Nest a Stack inside each tab group 3. **Trigger name mismatch**: Ensure `name` matches exact route name including parentheses 4. **Badge not visible**: Badge must be a child of Trigger, not a prop