rn-liquid-glass-bottom-tabs
v0.3.3
Published
Liquid Glass bottom tab bar for React Native (iOS 26+)
Readme
A drop-in iOS 26 liquid-glass bottom tab bar for React Native — with a draggable droplet pill, ripple animation, and configurable spring/timing animations.
Demo
- ▶️ iOS 26 liquid glass — the genuine
UIGlassEffectpath on iOS 26+ - ▶️ Legacy / fallback —
UITabBarController+UIBlurEffecton iOS 13–25
Which component should I use?
This package ships two tab bars. Pick the one that matches what you want:
| 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 |
LiquidGlassTabViewis a thin wrapper around SwiftUI's nativeTabViewon iOS 26 — you get Apple's tab bar exactly as it ships in iOS 26 apps. Icons are SF Symbol names (strings). On iOS < 26 it falls back to aUITabBarController(translucent system tab bar — the "previous liquid glass" look). On Android it falls back to a plain JS tab row.LiquidGlassTabBaris a fully-JS tab bar built on top ofLiquidGlassView/UIGlassEffect. Icons and labels are arbitraryReactNode(use any icon library, emoji, images). You get the draggable droplet, ripple animation, and full styling/animation control — but it's your bar, not the system one.
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.
Highlights
- 💧
LiquidGlassTabBar— bottom tab bar built on iOS 26's nativeUIGlassEffect - 👆 Drag-to-select — swipe across tabs; the droplet pill follows your finger and snaps to the nearest tab on release
- 🌊 Droplet ripple — tabs flex/scale as the droplet passes over them, like water deforming what's underneath
- 🪄 Magnify on grab — pill grows symmetrically (top/bottom/left/right) the moment you start dragging
- ⚙️ Configurable animation — spring or timing, with full config passthrough
- 🎨 Customizable — tint color, active tint color, color scheme, pill border radius, snap threshold
- 📱 3-tier fallback — iOS 26 liquid glass → iOS 13–25
UIBlurEffectmaterials /UITabBarController→ plain JS row on Android
Also re-exports the underlying primitives so you can build other glass UIs:
LiquidGlassTabView— native SwiftUI tab view (iOS 26) withUITabBarControllerfallbackLiquidGlassView— single glass surfaceLiquidGlassContainerView— merges sibling glass elements (the morph effect)createLiquidGlassTabNavigator— React Navigation integration for the native tab viewisLiquidGlassSupported— true when iOS 26 liquid glass APIs are availableisLegacyGlassSupported— true when the UIKit fallback (UIBlurEffect/UITabBarController) is available (any iOS)
Installation
npm install rn-liquid-glass-bottom-tabs
# or
yarn add rn-liquid-glass-bottom-tabs[!WARNING] Compile your app with Xcode ≥ 26 and React Native ≥ 0.80. New Architecture (Fabric) is required.
[!WARNING] Not supported in Expo Go — use a development build.
Quick start
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, anything).
Quick start — LiquidGlassTabView (native, system look)
Use this if 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><Text>Home screen</Text></View>
<View><Text>Search screen</Text></View>
<View><Text>Profile screen</Text></View>
</LiquidGlassTabView>
);
}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 to the iOS 26 SwiftUI path; it's ignored on the legacy fallback.
Using with React Navigation — native bar (createLiquidGlassTabNavigator)
If you want the genuine iOS 26 liquid glass tab bar wired up to React Navigation, use createLiquidGlassTabNavigator. It's a 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', // selected tab tint (bar-wide)
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>
);
}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, override per-screen. |
| tabBarMinimizeBehavior | 'automatic' \| 'never' \| 'onScrollDown' \| 'onScrollUp' | Scroll-driven minimize behavior. iOS 26 only; ignored on the legacy fallback.|
What renders per iOS version
| Version | Bar |
| ---------------- | ------------------------------------------------------------------- |
| 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 row (LiquidGlassTabViewFallback) |
What tabBarTintColor colors
- iOS 26: selected tab's icon + label tint (
UIView.tintColoron the SwiftUI host). - 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 — liquid glass reads from whatever is behind it. Put a colored view behind your screens if you want the bar to look tinted.
[!NOTE]
createLiquidGlassTabNavigatorrequires@react-navigation/nativeas a peer dependency. It's marked optional inpeerDependenciesMeta, so if you only useLiquidGlassTabBaryou don't need to install React Navigation.
Using with React Navigation — custom bar (LiquidGlassTabBar)
If you're using @react-navigation/bottom-tabs and want the custom draggable droplet bar instead of the native one, pass LiquidGlassTabBar as the tabBar prop. React Navigation handles routing, focus, deep linking, and screen lifecycles — LiquidGlassTabBar just renders the bar UI.
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:
- The mapping is straightforward:
state.routes→tabs,state.index→activeIndex, and tab presses are forwarded through React Navigation'snavigate(with thetabPressevent so screens can preventDefault — used for "scroll to top on tap" patterns). - Pull
tabBarIcon,tabBarLabel,tabBarAccessibilityLabelfrom each route'sdescriptor.optionsso users can configure tabs from<Tab.Screen>like they normally would. - Set
tabBarStyle: { display: 'none' }is not needed — React Navigation only renders one tab bar (the one you pass viatabBar). - For the screens not to be hidden behind the floating bar, give your screens
paddingBottom: 100(or useuseSafeAreaInsetsand add the bar height).
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 to the next tab |
| animation | { type: 'spring' \| 'timing' \| 'none'; config?: ... } | spring (iOS-like) | How the pill animates between tabs. Spring / timing configs are passed through to Animated |
| pillBorderRadius | number | 999 | Active-pill border radius. Default is fully rounded; lower for a more rectangular droplet |
| effect | 'clear' \| 'regular' \| 'none' | 'regular' | Glass effect of the active pill. The bar uses clear to keep the pill visually distinct |
| 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 underlying LiquidGlassContainerView merge effect |
| style | StyleProp<ViewStyle> | — | Outer wrapper style — use to position (e.g. absolute at the bottom) |
| tabStyle | StyleProp<ViewStyle> | — | Style for each tab's Pressable |
| activeTabStyle | StyleProp<ViewStyle> | — | Extra style for the active pill (merged with the animated transform) |
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
The package also exports the building blocks if you want to compose your own glass UIs:
import {
LiquidGlassView,
LiquidGlassContainerView,
isLiquidGlassSupported,
} 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) — the "previous liquid glass" look- Use
isLiquidGlassSupportedto detect the genuine iOS 26 path; useisLegacyGlassSupportedto detect any iOS (legacy or new). Both arefalseon Android.
Credits
Built on top of @callstack/liquid-glass by Callstack — the underlying UIGlassEffect Fabric component is theirs. This package adds a higher-level LiquidGlassTabBar with drag, ripple, and configurable animations.
License
MIT
Support the project 💛
If this saved you time and you'd like to contribute, you can support the project on Gumroad — every bit helps keep open-source maintenance going.
A ⭐ on the GitHub repo is also hugely appreciated — it helps others discover the project.
