rn-liquid-glass-bottom-tabs
v0.3.5
Published
Native iOS 26 liquid-glass bottom tabs for React Native — draggable droplet pill, ripple animation, React Navigation integration, 3-tier fallback
Maintainers
Readme
Native iOS 26 liquid-glass bottom tabs for React Native.
Draggable droplet pill · Ripple animation · React Navigation drop-in · 3-tier fallback.
Demo
| iOS 26 — native UIGlassEffect | iOS 13–25 — UITabBarController fallback |
| --- | --- |
| ▶ Watch on Google Drive | ▶ Watch on Kapwing |
Features
- 💧 Native iOS 26 liquid glass — wraps Apple's
UIGlassEffectdirectly via Fabric / New Architecture - 👆 Drag-to-select — swipe across tabs; the droplet pill follows your finger and snaps to the nearest tab on release
- 🌊 Ripple animation — tabs flex and scale as the droplet passes over them, like water deforming what's underneath
- 🪄 Magnify on grab — pill grows symmetrically the moment you start dragging
- ⚙️ Configurable animation — spring or timing, with full config passthrough to
Animated - 🎨 Fully customizable — tint color, color scheme, pill border radius, snap threshold, arbitrary icon/label
ReactNode - 📱 3-tier fallback — iOS 26
UIGlassEffect→ iOS 13–25UIBlurEffect/UITabBarController→ plain JS row on Android - 🧭 React Navigation integration —
createLiquidGlassTabNavigatoris a drop-in forcreateBottomTabNavigator
Requirements
| Requirement | Minimum | | --- | --- | | React Native | ≥ 0.80 with New Architecture (Fabric) enabled | | Xcode | 26 | | iOS at runtime | 13+ (liquid glass requires iOS 26+) | | React | ≥ 18 |
New Architecture: This library uses Fabric native components and TurboModules. If you are still on the old architecture, follow the migration guide before installing.
Expo: Not compatible with Expo Go. Use a development build or Expo Prebuild (bare workflow). See Expo below.
Android: Gets a plain JavaScript tab row — the same API works everywhere; only the glass effect is iOS-only.
Which component should I use?
| Want… | Use |
| --- | --- |
| The exact iOS 26 liquid glass tab bar (system look, SF Symbols, scroll-to-minimize, badges) | LiquidGlassTabView |
| Custom icons / labels (any ReactNode), draggable droplet, ripple, custom animation & styling | LiquidGlassTabBar |
LiquidGlassTabViewwraps SwiftUI's nativeTabViewon iOS 26 — you get Apple's tab bar exactly as it ships. Icons are SF Symbol names (strings). On iOS < 26 it falls back toUITabBarController. On Android it renders a plain JS row.LiquidGlassTabBaris a fully-JS tab bar built onLiquidGlassView/UIGlassEffect. Icons and labels are arbitraryReactNode(use any icon library, emoji, images). You get the draggable droplet, ripple animation, and full styling/animation control.
If you're integrating with React Navigation and want the standard iOS tab bar UX, use createLiquidGlassTabNavigator (built on LiquidGlassTabView). If you want a bespoke, branded tab bar, use LiquidGlassTabBar as the navigator's tabBar prop.
Installation
npm install rn-liquid-glass-bottom-tabs
# or
yarn add rn-liquid-glass-bottom-tabsNo additional pod install step is needed — CocoaPods picks up the podspec automatically on the next build.
Quick start — LiquidGlassTabBar (custom, draggable)
import { useState } from 'react';
import { Text } from 'react-native';
import { LiquidGlassTabBar } from 'rn-liquid-glass-bottom-tabs';
export function BottomTabs() {
const [activeIndex, setActiveIndex] = useState(0);
const tabs = [
{ key: 'home', icon: <Text>🏠</Text>, label: <Text>Home</Text> },
{ key: 'search', icon: <Text>🔍</Text>, label: <Text>Search</Text> },
{ key: 'profile', icon: <Text>👤</Text>, label: <Text>Profile</Text> },
];
return (
<LiquidGlassTabBar
tabs={tabs}
activeIndex={activeIndex}
onTabPress={setActiveIndex}
draggable
animation={{
type: 'spring',
config: { damping: 16, stiffness: 220, mass: 0.8 },
}}
pillBorderRadius={28}
style={{ position: 'absolute', bottom: 50, left: 20, right: 20 }}
/>
);
}Icons and labels are arbitrary ReactNode — bring your own icon library (Ionicons, Lucide, SF Symbols, emoji, images, anything).
Quick start — LiquidGlassTabView (native, system look)
Use this when you want the exact iOS 26 liquid glass tab bar. Icons are SF Symbol names.
import { useState } from 'react';
import { View, Text } from 'react-native';
import { LiquidGlassTabView } from 'rn-liquid-glass-bottom-tabs';
export function NativeTabs() {
const [selectedKey, setSelectedKey] = useState('home');
return (
<LiquidGlassTabView
tabs={[
{ key: 'home', title: 'Home', sfSymbol: 'house.fill' },
{ key: 'search', title: 'Search', sfSymbol: 'magnifyingglass' },
{ key: 'profile', title: 'Profile', sfSymbol: 'person.fill', badge: '3' },
]}
selectedKey={selectedKey}
onTabChange={setSelectedKey}
minimizeBehavior="onScrollDown"
tintColor="#007AFF"
>
<View collapsable={false}><Text>Home screen</Text></View>
<View collapsable={false}><Text>Search screen</Text></View>
<View collapsable={false}><Text>Profile screen</Text></View>
</LiquidGlassTabView>
);
}[!IMPORTANT] Wrap each tab screen's root view in
collapsable={false}.
LiquidGlassTabViewhosts your React Native screens inside native iOSUIViewControllers. React Native's view-flattening optimization can collapse a screen's outermost<View>— but that leaves the host view controller with no view to mount, so the screen renders blank or with broken layout.Fix: add
collapsable={false}to the outermost view of every screen:function HomeScreen() { return ( <View collapsable={false} style={{ flex: 1 }}> {/* screen content */} </View> ); }
What you get per iOS version:
| iOS version | What renders |
| --- | --- |
| iOS 26+ | SwiftUI TabView with the genuine liquid glass tab bar |
| iOS 13–25 | UIKit UITabBarController with the translucent system tab bar |
| Android / other | Plain JS tab row (LiquidGlassTabViewFallback) |
minimizeBehavior ('automatic' | 'never' | 'onScrollDown' | 'onScrollUp') only applies on iOS 26+; it's ignored on the fallback.
React Navigation — native bar (createLiquidGlassTabNavigator)
Drop-in replacement for createBottomTabNavigator that renders LiquidGlassTabView under the hood.
import { NavigationContainer } from '@react-navigation/native';
import { createLiquidGlassTabNavigator } from 'rn-liquid-glass-bottom-tabs';
const Tab = createLiquidGlassTabNavigator();
export function Tabs() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={{
tabBarTintColor: '#FF3B30',
tabBarMinimizeBehavior: 'onScrollDown', // iOS 26 only
}}
>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Home', sfSymbol: 'house.fill' }}
/>
<Tab.Screen
name="Search"
component={SearchScreen}
options={{ title: 'Search', sfSymbol: 'magnifyingglass' }}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{ title: 'Profile', sfSymbol: 'person.fill', badge: '3' }}
/>
</Tab.Navigator>
</NavigationContainer>
);
}[!IMPORTANT] Each screen component must have
collapsable={false}on its outermost view — see the note above.
[!NOTE]
createLiquidGlassTabNavigatorrequires@react-navigation/nativeas a peer dependency. It's marked optional — if you only useLiquidGlassTabBaryou don't need React Navigation installed.
Per-screen options
| Option | Type | Description |
| --- | --- | --- |
| title | string | Tab label. Falls back to the route name. |
| sfSymbol | string | SF Symbol name for the tab icon (e.g. 'house.fill'). |
| badge | string | Badge text shown on the tab (e.g. '3', 'NEW'). |
| tabBarTintColor | ColorValue | Selected-tab tint. Bar-wide — set in screenOptions. |
| tabBarMinimizeBehavior | 'automatic' \| 'never' \| 'onScrollDown' \| 'onScrollUp' | Scroll-driven minimize. iOS 26 only. |
What tabBarTintColor colors
- iOS 26: selected tab's icon + label tint.
- iOS 13–25:
UITabBar.tintColor— selected tab's icon/label color. - Android / JS fallback: active tab label color.
The glass background itself isn't tintable — put a colored view behind your screens if you want the bar to look tinted.
React Navigation — custom bar (LiquidGlassTabBar)
Use @react-navigation/bottom-tabs and pass LiquidGlassTabBar as the tabBar prop to get the draggable droplet bar instead of the native one.
import { Text } from 'react-native';
import {
createBottomTabNavigator,
type BottomTabBarProps,
} from '@react-navigation/bottom-tabs';
import { LiquidGlassTabBar } from 'rn-liquid-glass-bottom-tabs';
const Tab = createBottomTabNavigator();
function GlassTabBar({ state, descriptors, navigation }: BottomTabBarProps) {
const tabs = state.routes.map((route) => {
const { options } = descriptors[route.key];
const label =
typeof options.tabBarLabel === 'string'
? options.tabBarLabel
: (options.title ?? route.name);
return {
key: route.key,
icon: options.tabBarIcon?.({ focused: false, color: 'white', size: 22 }),
label: <Text style={{ color: 'white', fontSize: 13 }}>{label}</Text>,
accessibilityLabel: options.tabBarAccessibilityLabel,
};
});
return (
<LiquidGlassTabBar
tabs={tabs}
activeIndex={state.index}
onTabPress={(index) => {
const route = state.routes[index];
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.navigate(route.name, route.params);
}
}}
draggable
style={{ position: 'absolute', bottom: 30, left: 20, right: 20 }}
/>
);
}
export function Tabs() {
return (
<Tab.Navigator
screenOptions={{ headerShown: false }}
tabBar={(props) => <GlassTabBar {...props} />}
>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
tabBarLabel: 'Home',
}}
/>
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}Notes:
state.routes→tabs,state.index→activeIndex; tab presses go through React Navigation'snavigatesotabPressevents work (scroll-to-top pattern, etc.)- For screens not to hide behind the floating bar, add
paddingBottom: 100(or useuseSafeAreaInsets+ bar height) tabBarStyle: { display: 'none' }is not needed — React Navigation only renders the bar you pass viatabBar
LiquidGlassTabBar props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| tabs | LiquidGlassTabBarItem[] | — | Array of { key, icon?, label?, accessibilityLabel? } |
| activeIndex | number | — | Index of the currently active tab |
| onTabPress | (index, tab) => void | — | Fires on tap and on drag-snap |
| draggable | boolean | false | Enable drag-to-select with droplet ripple + magnify |
| dragSnapThreshold | number (0–1) | 0.5 | Fraction of a tab's width the drag must cross before snapping |
| animation | LiquidGlassTabBarAnimation | spring (iOS-like) | How the pill animates between tabs |
| pillBorderRadius | number | 999 | Active-pill border radius. Default is fully rounded |
| effect | 'clear' \| 'regular' \| 'none' | 'regular' | Glass effect of the active pill |
| tintColor | ColorValue | undefined | Tint for the bar background |
| activeTintColor | ColorValue | undefined | Tint for the active pill (falls back to tintColor) |
| colorScheme | 'light' \| 'dark' \| 'system' | 'system' | Override the glass color scheme |
| spacing | number | 20 | Distance for the LiquidGlassContainerView merge effect |
| style | StyleProp<ViewStyle> | — | Outer wrapper style — use to position absolutely |
| tabStyle | StyleProp<ViewStyle> | — | Style for each tab's Pressable |
| activeTabStyle | StyleProp<ViewStyle> | — | Extra style for the active pill |
Animation prop reference
type LiquidGlassTabBarAnimation =
| { type: 'spring'; config?: { damping?: number; stiffness?: number; mass?: number; overshootClamping?: boolean } }
| { type: 'timing'; config?: { duration?: number; easing?: (v: number) => number } }
| { type: 'none' }; // sets value instantly, no animationLower-level primitives
import {
LiquidGlassView,
LiquidGlassContainerView,
isLiquidGlassSupported,
isLegacyGlassSupported,
} from 'rn-liquid-glass-bottom-tabs';
if (!isLiquidGlassSupported) {
// Render a non-glass fallback
}
<LiquidGlassContainerView spacing={20}>
<LiquidGlassView interactive effect="clear" style={{ width: 100, height: 100, borderRadius: 50 }} />
<LiquidGlassView interactive effect="clear" style={{ width: 100, height: 100, borderRadius: 50 }} />
</LiquidGlassContainerView>For text inside glass that auto-adapts to the surface behind it, use PlatformColor:
import { PlatformColor, Text } from 'react-native';
import { LiquidGlassView } from 'rn-liquid-glass-bottom-tabs';
<LiquidGlassView style={{ padding: 20, borderRadius: 20 }}>
<Text style={{ color: PlatformColor('labelColor') }}>Hello World</Text>
</LiquidGlassView>[!NOTE] Fallback behavior (iOS only — Android always renders a plain
View):
- iOS 26+ → real liquid glass via
UIGlassEffect- iOS 13–25 →
UIBlurEffectwith system materials (systemUltraThinMaterialforregular,systemThinMaterialforclear)isLiquidGlassSupported→trueonly on iOS 26 withUIGlassEffectisLegacyGlassSupported→trueon any iOS (legacy or new);falseon Android
Expo
This library requires native code and does not work in Expo Go.
Development builds (recommended):
npx expo install rn-liquid-glass-bottom-tabs
npx expo prebuild # regenerates native ios/ folder
npx expo run:ios # builds and runs on simulator/deviceBare workflow: follows the standard installation steps above.
Expo managed workflow: not supported. The library requires New Architecture and Xcode 26, which Expo SDK's managed runtime doesn't yet expose.
Troubleshooting
Screen renders blank / stretched inside LiquidGlassTabView
Add collapsable={false} to the outermost <View> of each tab screen. See the note above.
Glass effect not showing on iOS
- Confirm you're running on a device or simulator with iOS 26+.
- Check
isLiquidGlassSupportedat runtime — it returnsfalseif the device runs iOS < 26 or ifUIDesignRequiresCompatibilityis set toYESin yourInfo.plist. - On iOS 13–25 you'll get the
UIBlurEffectfallback instead (also a glass look, just not the iOS 26 morph).
Build error: "No such module 'React'"
Run pod install inside example/ios/ (or your app's ios/ folder) after installing.
"NativeLiquidGlassModule not found"
New Architecture must be enabled. Add RCT_NEW_ARCH_ENABLED=1 to your .xcode.env.local (or set newArchEnabled=true in android/gradle.properties) and rebuild.
Module works on iOS but tab shows blank on first render
Add collapsable={false} to the screen root view. This is the most common issue.
Contributing
See CONTRIBUTING.md for the development workflow, commit conventions, and pull request process.
Quick start:
git clone https://github.com/Kashifstar151/rn-liquid-glass-bottom-tabs.git
cd rn-liquid-glass-bottom-tabs
yarn # installs all workspace deps
yarn example ios # build and run the example appCredits
Inspired by and initially built on top of @callstack/liquid-glass by Callstack, which pioneered the UIGlassEffect Fabric component approach for React Native. This package adds a higher-level LiquidGlassTabBar with drag-to-select, ripple, configurable animations, and a createLiquidGlassTabNavigator React Navigation integration.
Changelog
See CHANGELOG.md for release history.
License
MIT © Kashif Khan
If this saved you time, a ⭐ on GitHub is hugely appreciated — it helps others discover the project.
