diff --git a/.changeset/calm-icons-preserve.md b/.changeset/calm-icons-preserve.md
new file mode 100644
index 00000000..3e2c0ec7
--- /dev/null
+++ b/.changeset/calm-icons-preserve.md
@@ -0,0 +1,6 @@
+---
+'react-native-bottom-tabs': minor
+'@bottom-tabs/react-navigation': minor
+---
+
+Add an icon rendering mode option for preserving original colors of tab icons.
diff --git a/apps/example/assets/avatar-3.png b/apps/example/assets/avatar-3.png
new file mode 100644
index 00000000..82ba4541
Binary files /dev/null and b/apps/example/assets/avatar-3.png differ
diff --git a/apps/example/assets/avatar-4.png b/apps/example/assets/avatar-4.png
new file mode 100644
index 00000000..e326ebe9
Binary files /dev/null and b/apps/example/assets/avatar-4.png differ
diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock
index 44495999..28dbfaf0 100644
--- a/apps/example/ios/Podfile.lock
+++ b/apps/example/ios/Podfile.lock
@@ -2884,6 +2884,6 @@ SPEC CHECKSUMS:
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d
-PODFILE CHECKSUM: 94c446c3bbb7f59f580c73dea5e50cf22b6ff990
+PODFILE CHECKSUM: e153d64da2cc12b425726c29ab2a7d70a1671df5
COCOAPODS: 1.16.2
diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx
index 90d7ee9f..5e70681a 100644
--- a/apps/example/src/App.tsx
+++ b/apps/example/src/App.tsx
@@ -43,6 +43,8 @@ import BottomAccessoryView from './Examples/BottomAccessoryView';
import TabBarHidden from './Examples/TabBarHidden';
import CustomTabBar from './Examples/CustomTabBar';
import NativeBottomTabsTabBarHidden from './Examples/NativeBottomTabsTabBarHidden';
+import OriginalIconColors from './Examples/OriginalIconColors';
+import NativeBottomTabsOriginalIcons from './Examples/NativeBottomTabsOriginalIcons';
import { useLogger } from '@react-navigation/devtools';
import LazyTabs from './Examples/LazyTabs';
import { LogBox } from 'react-native';
@@ -118,6 +120,10 @@ const examples = [
component: CustomTabBar,
name: 'Custom tabBar',
},
+ {
+ component: OriginalIconColors,
+ name: 'Original icon colors',
+ },
{
component: FourTabsRippleColor,
name: 'Four Tabs with ripple Color',
@@ -186,6 +192,10 @@ const examples = [
component: NativeBottomTabsTabBarHidden,
name: 'Native Bottom Tabs with tabBarHidden',
},
+ {
+ component: NativeBottomTabsOriginalIcons,
+ name: 'Native Bottom Tabs with original icons',
+ },
{ component: NativeBottomTabs, name: 'Native Bottom Tabs' },
{ component: JSBottomTabs, name: 'JS Bottom Tabs' },
{
diff --git a/apps/example/src/Examples/NativeBottomTabsOriginalIcons.tsx b/apps/example/src/Examples/NativeBottomTabsOriginalIcons.tsx
new file mode 100644
index 00000000..fddf733b
--- /dev/null
+++ b/apps/example/src/Examples/NativeBottomTabsOriginalIcons.tsx
@@ -0,0 +1,48 @@
+import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation';
+import { Article } from '../Screens/Article';
+import { Albums } from '../Screens/Albums';
+import { Contacts } from '../Screens/Contacts';
+import { Chat } from '../Screens/Chat';
+
+const Tab = createNativeBottomTabNavigator();
+
+export default function NativeBottomTabsOriginalIcons() {
+ return (
+
+ require('../../assets/avatar-4.png'),
+ }}
+ />
+ require('../../assets/avatar-1.png'),
+ tabBarIconRenderingMode: 'original',
+ }}
+ />
+
+ focused
+ ? require('../../assets/avatar-4.png')
+ : require('../../assets/avatar-3.png'),
+ tabBarIconRenderingMode: 'original',
+ }}
+ />
+ require('../../assets/avatar-4.png'),
+ tabBarIconRenderingMode: 'original',
+ }}
+ />
+
+ );
+}
diff --git a/apps/example/src/Examples/OriginalIconColors.tsx b/apps/example/src/Examples/OriginalIconColors.tsx
new file mode 100644
index 00000000..b67a83be
--- /dev/null
+++ b/apps/example/src/Examples/OriginalIconColors.tsx
@@ -0,0 +1,51 @@
+import TabView, { SceneMap } from 'react-native-bottom-tabs';
+import { useState } from 'react';
+import { Article } from '../Screens/Article';
+import { Albums } from '../Screens/Albums';
+import { Contacts } from '../Screens/Contacts';
+import { Chat } from '../Screens/Chat';
+
+const renderScene = SceneMap({
+ tinted: Article,
+ avatar: Contacts,
+ album: Albums,
+ chat: Chat,
+});
+
+export default function OriginalIconColors() {
+ const [index, setIndex] = useState(0);
+ const [routes] = useState([
+ {
+ key: 'tinted',
+ title: 'Tinted',
+ focusedIcon: require('../../assets/avatar-4.png'),
+ },
+ {
+ key: 'avatar',
+ title: 'Avatar',
+ focusedIcon: require('../../assets/avatar-1.png'),
+ iconRenderingMode: 'original',
+ },
+ {
+ key: 'album',
+ title: 'Album',
+ unfocusedIcon: require('../../assets/avatar-3.png'),
+ focusedIcon: require('../../assets/avatar-4.png'),
+ iconRenderingMode: 'original',
+ },
+ {
+ key: 'chat',
+ title: 'Chat',
+ focusedIcon: require('../../assets/avatar-4.png'),
+ iconRenderingMode: 'original',
+ },
+ ]);
+
+ return (
+
+ );
+}
diff --git a/apps/example/src/Examples/TintColors.tsx b/apps/example/src/Examples/TintColors.tsx
index 61351eaa..62c87340 100644
--- a/apps/example/src/Examples/TintColors.tsx
+++ b/apps/example/src/Examples/TintColors.tsx
@@ -29,9 +29,10 @@ export default function TintColorsExample() {
{
key: 'albums',
title: 'Albums',
- focusedIcon: require('../../assets/icons/grid_dark.png'),
- badge: '5',
- activeTintColor: 'green',
+ unfocusedIcon: require('../../assets/avatar-3.png'),
+ focusedIcon: require('../../assets/avatar-4.png'),
+ activeTintColor: 'purple',
+ iconRenderingMode: 'original',
},
{
key: 'contacts',
diff --git a/docs/docs/docs/guides/standalone-usage.mdx b/docs/docs/docs/guides/standalone-usage.mdx
index 59a6e5c2..d9f02c43 100644
--- a/docs/docs/docs/guides/standalone-usage.mdx
+++ b/docs/docs/docs/guides/standalone-usage.mdx
@@ -236,6 +236,7 @@ Each route in the `routes` array can have the following properties:
- `title`: Display title for the tab
- `focusedIcon`: Icon to show when tab is active
- `unfocusedIcon`: Icon to show when tab is inactive (optional)
+- `iconRenderingMode`: Rendering mode for icons. Use `'original'` to preserve multicolor icons instead of applying the native tab tint.
- `badge`: Badge text to display on the tab
- `activeTintColor`: Custom active tint color for this specific tab
- `lazy`: Whether to lazy load this tab's content
@@ -280,6 +281,15 @@ Function to get the icon for a tab.
- Default: Uses `route.focusedIcon` and `route.unfocusedIcon`
+#### `getIconRenderingMode`
+
+Function to get the rendering mode for an image icon.
+
+- Default: Uses `route.iconRenderingMode`
+- Options: `'automatic' | 'original'`
+
+Use `original` to preserve the original colors of multicolor image icons, such as logos or branded assets, instead of applying the native tab tint.
+
#### `getHidden`
Function to determine if a tab should be hidden.
diff --git a/docs/docs/docs/guides/usage-with-react-navigation.mdx b/docs/docs/docs/guides/usage-with-react-navigation.mdx
index d7bda582..78e49d55 100644
--- a/docs/docs/docs/guides/usage-with-react-navigation.mdx
+++ b/docs/docs/docs/guides/usage-with-react-navigation.mdx
@@ -185,6 +185,7 @@ Controls how the tab bar behaves when content is scrolled.
- Default: `undefined` (uses system default)
Options:
+
- `automatic`: Platform determines the behavior
- `onScrollDown`: Tab bar minimizes when scrolling down
- `onScrollUp`: Tab bar minimizes when scrolling up
@@ -225,15 +226,14 @@ function MyTabBar({ state, descriptors, navigation }) {
function MyTabs() {
return (
- }
- >
+ }>
);
}
```
+
#### `renderBottomAccessoryView`
Function that returns a React element to render as [bottom accessory](https://developer.apple.com/documentation/uikit/uitabbarcontroller/bottomaccessory).
@@ -286,9 +286,29 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon`
SF Symbols are only supported on Apple platforms.
:::
+#### `tabBarIconRenderingMode`
+
+Rendering mode for image icons.
+
+- Type: `'automatic' | 'original'`
+- Default: `'automatic'`
+
+Use `original` to preserve the original colors of multicolor image icons, such as logos or branded assets, instead of applying the native tab tint.
+
+```tsx
+ require('brand-logo.png'),
+ tabBarIconRenderingMode: 'original',
+ }}
+/>
+```
+
:::warning
-Metro transformers that modify the import resolutions of images to something that is *not* React Native's `ImageSource` will break this. A notable community example of such is [react-native-svg-transformer](https://github.com/kristerkari/react-native-svg-transformer).
+Metro transformers that modify the import resolutions of images to something that is _not_ React Native's `ImageSource` will break this. A notable community example of such is [react-native-svg-transformer](https://github.com/kristerkari/react-native-svg-transformer).
For best results, avoid the use of such transformers altogether.
@@ -312,13 +332,13 @@ config.resolver.sourceExts = [...config.resolver.sourceExts, "svg"];
```
Then change your require calls like so:
+
```
tabBarIcon: () => require('person.svgx')
```
:::
-
#### `tabBarBadge`
Badge to show on the tab icon.
@@ -329,14 +349,14 @@ To display a badge without text (just a dot), you need to pass a string with a s
#### `tabBarBadgeBackgroundColor`
- - Type: `string`
+- Type: `string`
Set the background color for the badge on android.
Uses the system color by default.
#### `tabBarBadgeTextColor`
- - Type: `string`
+- Type: `string`
Set the text color for the badge on android.
Uses the system color by default.
@@ -356,6 +376,7 @@ Due to native limitations on iOS, this option doesn't hide the tab item **when h
Whether this screens should render the first time it's accessed. Defaults to true. Set it to false if you want to render the screen on initial render.
#### `freezeOnBlur`
+
Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to false.
It's working separately from `enableFreeze()` in `react-native-screens`. So settings won't be shared between them.
@@ -371,7 +392,6 @@ Whether to prevent default tab switching behavior when this tab is pressed. This
Due to iOS 26's new tab switching animations, controlling tab switching from JavaScript can cause significant delays. The `preventsDefault` option allows you to define this behavior statically to avoid animation delays.
:::
-
#### `tabBarButtonTestID`
Test ID for the tab item. This can be used to find the tab item in the native view hierarchy.
@@ -413,7 +433,6 @@ React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', (e) => {
// Note: For iOS 26+, use the `preventsDefault` option instead of `e.preventDefault()`
// to avoid animation delays
-
// Do something manually
// ...
});
diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt
index 1d0c4047..911d2789 100644
--- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt
+++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
+import android.graphics.PorterDuff
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
@@ -19,6 +20,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
+import androidx.core.view.MenuItemCompat
import androidx.core.view.forEachIndexed
import coil3.ImageLoader
import coil3.asDrawable
@@ -246,9 +248,11 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
}
menuItem.isVisible = !item.hidden
+ updateIconTintMode(menuItem, item)
if (iconSources.containsKey(index)) {
getDrawable(iconSources[index]!!) {
menuItem.icon = it
+ updateIconTintMode(menuItem, item)
}
}
@@ -297,6 +301,13 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
return bottomNavigation.menu.findItem(index) ?: bottomNavigation.menu.add(0, index, 0, title)
}
+ private fun updateIconTintMode(menuItem: MenuItem, item: TabInfo) {
+ MenuItemCompat.setIconTintMode(
+ menuItem,
+ if (item.iconRenderingMode == "original") PorterDuff.Mode.DST else null
+ )
+ }
+
fun setIcons(icons: ReadableArray?) {
if (icons == null || icons.size() == 0) {
return
@@ -316,6 +327,9 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
bottomNavigation.menu.findItem(idx)?.let { menuItem ->
getDrawable(imageSource) {
menuItem.icon = it
+ items.getOrNull(idx)?.let { item ->
+ updateIconTintMode(menuItem, item)
+ }
}
}
}
diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
index 95644dbb..6451db01 100644
--- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
+++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
@@ -24,6 +24,7 @@ data class TabInfo(
val badgeBackgroundColor: Int?,
val badgeTextColor: Int?,
val activeTintColor: Int?,
+ val iconRenderingMode: String?,
val hidden: Boolean,
val testID: String?
)
@@ -104,6 +105,7 @@ class RCTTabViewManager(context: ReactApplicationContext) :
badgeBackgroundColor = if (item.hasKey("badgeBackgroundColor")) item.getInt("badgeBackgroundColor") else null,
badgeTextColor = if (item.hasKey("badgeTextColor")) item.getInt("badgeTextColor") else null,
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
+ iconRenderingMode = if (item.hasKey("iconRenderingMode")) item.getString("iconRenderingMode") else null,
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false,
testID = item.getString("testID")
)
diff --git a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
index bd8edcef..f25b3585 100644
--- a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
+++ b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
@@ -37,6 +37,7 @@
lhs.sfSymbol == rhs.sfSymbol &&
lhs.badge == rhs.badge &&
lhs.activeTintColor == rhs.activeTintColor &&
+ lhs.iconRenderingMode == rhs.iconRenderingMode &&
lhs.hidden == rhs.hidden &&
lhs.testID == rhs.testID &&
lhs.role == rhs.role &&
@@ -197,6 +198,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
badge:RCTNSStringFromStringNilIfEmpty(item.badge)
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
+ iconRenderingMode:RCTNSStringFromStringNilIfEmpty(item.iconRenderingMode)
hidden:item.hidden
testID:RCTNSStringFromStringNilIfEmpty(item.testID)
role:RCTNSStringFromStringNilIfEmpty(item.role)
@@ -265,4 +267,3 @@ - (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag {
}
#endif // RCT_NEW_ARCH_ENABLED
-
diff --git a/packages/react-native-bottom-tabs/ios/TabItem.swift b/packages/react-native-bottom-tabs/ios/TabItem.swift
index 3866c59b..3992f27d 100644
--- a/packages/react-native-bottom-tabs/ios/TabItem.swift
+++ b/packages/react-native-bottom-tabs/ios/TabItem.swift
@@ -5,13 +5,14 @@ struct TabItem: View {
var icon: PlatformImage?
var sfSymbol: String?
var labeled: Bool?
+ var iconRenderingMode: String?
var body: some View {
if let icon {
#if os(macOS)
Image(nsImage: icon)
#else
- Image(uiImage: icon)
+ Image(uiImage: renderedIcon(icon))
#endif
} else if let sfSymbol, !sfSymbol.isEmpty {
Image(systemName: sfSymbol)
@@ -21,4 +22,14 @@ struct TabItem: View {
Text(title ?? "")
}
}
+
+#if !os(macOS)
+ private var preservesOriginalIconColors: Bool {
+ iconRenderingMode == "original"
+ }
+
+ private func renderedIcon(_ icon: UIImage) -> UIImage {
+ preservesOriginalIconColors ? icon.withRenderingMode(.alwaysOriginal) : icon
+ }
+#endif
}
diff --git a/packages/react-native-bottom-tabs/ios/TabView/LegacyTabView.swift b/packages/react-native-bottom-tabs/ios/TabView/LegacyTabView.swift
index 5bfc58f4..188ddfde 100644
--- a/packages/react-native-bottom-tabs/ios/TabView/LegacyTabView.swift
+++ b/packages/react-native-bottom-tabs/ios/TabView/LegacyTabView.swift
@@ -56,7 +56,8 @@ struct LegacyTabView: AnyTabView {
title: tabData.title,
icon: icon,
sfSymbol: tabData.sfSymbol,
- labeled: props.labeled
+ labeled: props.labeled,
+ iconRenderingMode: tabData.iconRenderingMode
)
.accessibilityIdentifier(tabData.testID ?? "")
}
diff --git a/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
index 9e8dd80a..2fdc681c 100644
--- a/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
+++ b/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
@@ -48,7 +48,8 @@ struct NewTabView: AnyTabView {
title: tabData.title,
icon: icon,
sfSymbol: tabData.sfSymbol,
- labeled: props.labeled
+ labeled: props.labeled,
+ iconRenderingMode: tabData.iconRenderingMode
)
}
#if !os(tvOS)
diff --git a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift
index 14732fdd..dee9afa4 100644
--- a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift
+++ b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift
@@ -229,6 +229,7 @@ struct TabViewImpl: View {
let tabActiveColor = tabData.activeTintColor ?? props.activeTintColor
let assetIcon = props.icons[itemIndex]
let icon = assetIcon ?? makeSFSymbolImage(named: tabData.sfSymbol)
+ let preservesOriginalIconColors = preservesOriginalIconColors(tabData: tabData)
let shouldRenderLabelIntoImage =
props.hasCustomTintColors && props.labeled && tabData.role != .search && icon != nil
@@ -241,12 +242,14 @@ struct TabViewImpl: View {
icon: icon,
title: tabData.title,
color: props.inactiveTintColor,
+ preservesOriginalIconColors: preservesOriginalIconColors,
props: props
)
item.selectedImage = makeTabBarItemImage(
icon: icon,
title: tabData.title,
color: tabActiveColor,
+ preservesOriginalIconColors: preservesOriginalIconColors,
props: props
)
continue
@@ -256,14 +259,20 @@ struct TabViewImpl: View {
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 0)
if let icon {
- item.image =
- props.inactiveTintColor.map {
- icon.withTintColor($0, renderingMode: .alwaysOriginal)
- } ?? icon
- item.selectedImage =
- tabActiveColor.map {
- icon.withTintColor($0, renderingMode: .alwaysOriginal)
- } ?? icon
+ if preservesOriginalIconColors {
+ let originalIcon = icon.withRenderingMode(.alwaysOriginal)
+ item.image = originalIcon
+ item.selectedImage = originalIcon
+ } else {
+ item.image =
+ props.inactiveTintColor.map {
+ icon.withTintColor($0, renderingMode: .alwaysOriginal)
+ } ?? icon
+ item.selectedImage =
+ tabActiveColor.map {
+ icon.withTintColor($0, renderingMode: .alwaysOriginal)
+ } ?? icon
+ }
}
item.setTitleTextAttributes(
@@ -290,14 +299,21 @@ struct TabViewImpl: View {
let assetIcon = props.icons[itemIndex]
let icon = assetIcon ?? makeSFSymbolImage(named: tabData.sfSymbol)
+ let originalIcon = icon.map {
+ preservesOriginalIconColors(tabData: tabData) ? $0.withRenderingMode(.alwaysOriginal) : $0
+ }
item.title = props.labeled ? tabData.title : nil
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 0)
- item.image = icon
- item.selectedImage = icon
+ item.image = originalIcon
+ item.selectedImage = originalIcon
}
}
+ private func preservesOriginalIconColors(tabData: TabInfo) -> Bool {
+ tabData.iconRenderingMode == "original"
+ }
+
private func makeSFSymbolImage(named sfSymbol: String?) -> UIImage? {
guard let sfSymbol, !sfSymbol.isEmpty else { return nil }
@@ -331,6 +347,7 @@ struct TabViewImpl: View {
icon: UIImage,
title: String,
color: UIColor?,
+ preservesOriginalIconColors: Bool = false,
props: TabViewProps
) -> UIImage {
let color = color ?? .label
@@ -357,7 +374,12 @@ struct TabViewImpl: View {
format.scale = UIScreen.main.scale
let image = UIGraphicsImageRenderer(size: imageSize, format: format).image { _ in
- let tintedIcon = icon.withTintColor(color, renderingMode: .alwaysOriginal)
+ let tintedIcon: UIImage
+ if preservesOriginalIconColors {
+ tintedIcon = icon.withRenderingMode(.alwaysOriginal)
+ } else {
+ tintedIcon = icon.withTintColor(color, renderingMode: .alwaysOriginal)
+ }
let iconFrame = aspectFitRect(
size: tintedIcon.size,
in: CGRect(
diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
index 799f3c0c..e6dd2a2e 100644
--- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
+++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
@@ -9,6 +9,7 @@ public final class TabInfo: NSObject {
public let badge: String?
public let sfSymbol: String
public let activeTintColor: PlatformColor?
+ public let iconRenderingMode: String?
public let hidden: Bool
public let testID: String?
public let role: TabBarRole?
@@ -20,6 +21,7 @@ public final class TabInfo: NSObject {
badge: String?,
sfSymbol: String,
activeTintColor: PlatformColor?,
+ iconRenderingMode: String?,
hidden: Bool,
testID: String?,
role: String?,
@@ -30,6 +32,7 @@ public final class TabInfo: NSObject {
self.badge = badge
self.sfSymbol = sfSymbol
self.activeTintColor = activeTintColor
+ self.iconRenderingMode = iconRenderingMode
self.hidden = hidden
self.testID = testID
self.role = TabBarRole(rawValue: role ?? "")
diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx
index e4f007e1..d9a1ccab 100644
--- a/packages/react-native-bottom-tabs/src/TabView.tsx
+++ b/packages/react-native-bottom-tabs/src/TabView.tsx
@@ -25,6 +25,7 @@ import useLatestCallback from 'use-latest-callback';
import type {
AppleIcon,
BaseRoute,
+ IconRenderingMode,
LayoutDirection,
NavigationState,
TabRole,
@@ -145,6 +146,15 @@ interface Props {
focused: boolean;
}) => ImageSource | AppleIcon | undefined | null;
+ /**
+ * Get the rendering mode for the tab icon, uses `route.iconRenderingMode` by default.
+ *
+ * Use `original` to preserve multicolor image icons instead of applying the native tab tint.
+ */
+ getIconRenderingMode?: (props: {
+ route: Route;
+ }) => IconRenderingMode | undefined;
+
/**
* Get hidden for the tab, uses `route.hidden` by default.
* If `true`, the tab will be hidden.
@@ -252,6 +262,8 @@ const TabView = ({
getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor,
getTestID = ({ route }: { route: Route }) => route.testID,
getRole = ({ route }: { route: Route }) => route.role,
+ getIconRenderingMode = ({ route }: { route: Route }) =>
+ route.iconRenderingMode,
getSceneStyle = ({ route }: { route: Route }) => route.style,
getPreventsDefault = ({ route }: { route: Route }) => route.preventsDefault,
hapticFeedbackEnabled = false,
@@ -331,6 +343,7 @@ const TabView = ({
),
badgeTextColor: processColor(getBadgeTextColor?.({ route })),
activeTintColor: processColor(getActiveTintColor({ route })),
+ iconRenderingMode: getIconRenderingMode({ route }),
hidden: getHidden?.({ route }),
testID: getTestID?.({ route }),
role: getRole?.({ route }),
@@ -345,6 +358,7 @@ const TabView = ({
getBadgeBackgroundColor,
getBadgeTextColor,
getActiveTintColor,
+ getIconRenderingMode,
getHidden,
getTestID,
getRole,
diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
index 4a4e81ad..387ab9f4 100644
--- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
+++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
@@ -30,6 +30,7 @@ export type TabViewItems = ReadonlyArray<{
badgeBackgroundColor?: ProcessedColorValue | null;
badgeTextColor?: ProcessedColorValue | null;
activeTintColor?: ProcessedColorValue | null;
+ iconRenderingMode?: string;
hidden?: boolean;
testID?: string;
role?: string;
diff --git a/packages/react-native-bottom-tabs/src/index.tsx b/packages/react-native-bottom-tabs/src/index.tsx
index f0487bc8..19544834 100644
--- a/packages/react-native-bottom-tabs/src/index.tsx
+++ b/packages/react-native-bottom-tabs/src/index.tsx
@@ -15,4 +15,9 @@ export { BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
/**
* Types
*/
-export type { AppleIcon, LayoutDirection, TabRole } from './types';
+export type {
+ AppleIcon,
+ IconRenderingMode,
+ LayoutDirection,
+ TabRole,
+} from './types';
diff --git a/packages/react-native-bottom-tabs/src/types.ts b/packages/react-native-bottom-tabs/src/types.ts
index 83a9b2bb..3f4eda80 100644
--- a/packages/react-native-bottom-tabs/src/types.ts
+++ b/packages/react-native-bottom-tabs/src/types.ts
@@ -7,6 +7,8 @@ export type AppleIcon = { sfSymbol: SFSymbol };
export type TabRole = 'search';
+export type IconRenderingMode = 'automatic' | 'original';
+
export type LayoutDirection = 'ltr' | 'rtl' | 'locale';
export type BaseRoute = {
@@ -18,6 +20,7 @@ export type BaseRoute = {
lazy?: boolean;
focusedIcon?: ImageSourcePropType | AppleIcon;
unfocusedIcon?: ImageSourcePropType | AppleIcon;
+ iconRenderingMode?: IconRenderingMode;
activeTintColor?: string;
hidden?: boolean;
testID?: string;
diff --git a/packages/react-navigation/src/types.ts b/packages/react-navigation/src/types.ts
index b55afb05..eb1967cd 100644
--- a/packages/react-navigation/src/types.ts
+++ b/packages/react-navigation/src/types.ts
@@ -9,7 +9,11 @@ import type {
} from '@react-navigation/native';
import type { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
import type TabView from 'react-native-bottom-tabs';
-import type { AppleIcon, TabRole } from 'react-native-bottom-tabs';
+import type {
+ AppleIcon,
+ IconRenderingMode,
+ TabRole,
+} from 'react-native-bottom-tabs';
export type NativeBottomTabNavigationEventMap = {
/**
@@ -67,6 +71,11 @@ export type NativeBottomTabNavigationOptions = {
*/
tabBarIcon?: (props: { focused: boolean }) => ImageSourcePropType | AppleIcon;
+ /**
+ * Rendering mode for the tab icon. Use `original` to preserve multicolor image icons.
+ */
+ tabBarIconRenderingMode?: IconRenderingMode;
+
/**
* Whether the tab bar item is visible. Defaults to true.
*/
@@ -150,6 +159,7 @@ export type NativeBottomTabNavigationConfig = Partial<
| 'renderScene'
| 'getLazy'
| 'getIcon'
+ | 'getIconRenderingMode'
| 'getLabelText'
| 'getBadge'
| 'getBadgeBackgroundColor'
diff --git a/packages/react-navigation/src/views/NativeBottomTabView.tsx b/packages/react-navigation/src/views/NativeBottomTabView.tsx
index 2bc2de4e..629bec4b 100644
--- a/packages/react-navigation/src/views/NativeBottomTabView.tsx
+++ b/packages/react-navigation/src/views/NativeBottomTabView.tsx
@@ -57,6 +57,9 @@ export default function NativeBottomTabView({
descriptors[route.key]?.options.tabBarButtonTestID
}
getRole={({ route }) => descriptors[route.key]?.options.role}
+ getIconRenderingMode={({ route }) =>
+ descriptors[route.key]?.options.tabBarIconRenderingMode
+ }
tabBar={
tabBar ? () => tabBar({ state, descriptors, navigation }) : undefined
}