# 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