Apple's Liquid Glass redesign made custom JavaScript tab bars look dated overnight. The good news is that Expo Router's NativeTabs renders the real system tab bar, so your app gets Liquid Glass on iOS 26+, Material styling on Android, and native behaviors like scroll-to-top without writing a single animation.
As of Expo SDK 58, native tabs are stable. This guide covers the current API.
JavaScript tabs vs native tabs
Expo Router gives you two ways to build tabs:
Tabs(fromexpo-router) is built on React Navigation's bottom tabs and drawn in JavaScript. It's fully customizable and looks the same everywhere.NativeTabs(fromexpo-router/native-tabs) uses the OS tab bar. It looks native, animates natively, and gets new system features (like Liquid Glass) automatically.
Rule of thumb: start with NativeTabs. Move to a custom bar only when the design truly needs something the system can't do.
Set up native tabs
Put your tab screens in a group and add a layout:
app/
_layout.tsx
(tabs)/
_layout.tsx
index.tsx
explore.tsx
settings.tsx
// app/(tabs)/_layout.tsx
import { NativeTabs } from "expo-router/native-tabs"
export default function TabLayout() {
return (
<NativeTabs>
<NativeTabs.Trigger name="index">
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="explore">
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="safari" md="explore" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="settings">
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
</NativeTabs.Trigger>
</NativeTabs>
)
}
Each Trigger's name matches a file in the folder. sf is the SF Symbol used on iOS and md is the Material Symbol used on Android, so you get platform-correct icons with no icon library.
On SDK 55–57, import from
expo-router/unstable-native-tabsinstead. The component API is the same.
Different icons for selected and unselected
Pass an object to switch icons based on state, for example an outline icon that fills in when the tab is active:
<NativeTabs.Trigger.Icon
sf={{ default: "bell", selected: "bell.fill" }}
md="notifications"
/>
Add a badge
Show unread counts with a badge. It's native, so it matches the system style exactly:
<NativeTabs.Trigger name="inbox">
<NativeTabs.Trigger.Label>Inbox</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="tray.fill" md="inbox" />
<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>
</NativeTabs.Trigger>
A dedicated search tab (iOS 26+)
On iOS 26 and later, a tab with role="search" is shown as a separate search button next to the Liquid Glass bar, the same pattern Apple's own apps use:
<NativeTabs.Trigger name="search" role="search">
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
</NativeTabs.Trigger>
Minimize the tab bar on scroll
Liquid Glass tab bars can shrink while the user scrolls content, which gives your screen more room:
<NativeTabs minimizeBehavior="onScrollDown">
{/* triggers */}
</NativeTabs>
Hide, disable, and tune tab behavior
hiddenremoves a tab from the bar entirely. This is handy for feature flags or role-based tabs.disabledblocks taps on the tab but still lets you navigate there from code.disablePopToTopanddisableScrollToTopturn off the default "tap the active tab to reset" behavior.tabBarRespectsIMEInsetslifts the Android tab bar above the keyboard.
<NativeTabs.Trigger name="admin" hidden={!user.isAdmin}>
<NativeTabs.Trigger.Label>Admin</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="lock.shield" md="admin_panel_settings" />
</NativeTabs.Trigger>
Stacks inside tabs
Most real apps need a stack inside each tab (list → detail). Make the tab a folder with its own _layout.tsx:
app/(tabs)/
_layout.tsx // NativeTabs
home/
_layout.tsx // Stack
index.tsx
[id].tsx
// app/(tabs)/home/_layout.tsx
import { Stack } from "expo-router"
export default function HomeStack() {
return <Stack />
}
The Trigger's name becomes home. For more on organizing routes like this, see our Expo Router folder structure guide.
When you need a custom tab bar instead
The system bar is deliberately limited. You can't put a floating action button in the middle, use a gradient background, or animate a pill indicator. If your design calls for that, use the JavaScript Tabs component with a custom tabBar.
Our free Animated Bottom Navigation component ships 11 styles behind one variant prop, including Glassmorphism, Floating, Center FAB, Curved, and an iOS-style Dock. Every animation runs on the native driver, so you get the custom look without dropped frames.
Next steps
- New to routing? Start with the Expo Router navigation guide.
- Want more Liquid Glass in your UI? Read Liquid Glass and glassmorphism in Expo.
- Upgrading? See what's new in Expo SDK 58.