Platform-Specific Code
React Native's whole promise is "learn once, write anywhere" — most of your components, styles, and logic really do run unchanged on both Android and iOS. But the two platforms are not identical operating systems wearing the same skin. Shadows work differently, certain UI conventions differ (status bar behavior, back navigation, default fonts), and some features simply only exist on one platform. React Native gives you a few clean, well-supported ways to handle those differences without turning your codebase into a mess of special cases.
The Platform Module and Platform.OS
The Platform module is the simplest tool for this. Its Platform.OS
property is a string that is always exactly 'ios' or 'android' at
runtime, depending on which device (or simulator/emulator) the app is currently running on. You can
use it directly in a ternary anywhere you need one value on iOS and a different value on Android.
import React from 'react';
import { View, Text, Platform } from 'react-native';
export default function HeaderBar() {
return (
<View
style={{
height: Platform.OS === 'ios' ? 44 : 56, // taller app bar on Android
backgroundColor: '#1a8cbf',
justifyContent: 'center',
paddingHorizontal: 16,
}}
>
<Text style={{ color: '#fff', fontWeight: '700' }}>
My App
</Text>
</View>
);
}
That works fine for a single value, but reaching for a ternary every time you need a platform-specific number gets noisy fast, especially once you have more than two options to choose between (say, a default value alongside the iOS/Android overrides).
A Cleaner Alternative: Platform.select()
Platform.select() takes an object keyed by platform name and returns whichever value
matches the current Platform.OS. It also supports a default key for any
platform that isn't explicitly listed (useful if the project also targets web via
react-native-web). This reads far more clearly than a chain of ternaries once you have several
platform-specific values to pick between.
import React from 'react';
import { View, StyleSheet, Platform } from 'react-native';
const styles = StyleSheet.create({
card: {
padding: 16,
borderRadius: 12,
backgroundColor: '#fff',
...Platform.select({
ios: {
fontFamily: 'Avenir',
shadowColor: '#000',
},
android: {
fontFamily: 'Roboto',
elevation: 4,
},
default: {
fontFamily: 'System',
},
}),
},
});
export default function Card({ children }) {
return <View style={styles.card}>{children}</View>;
}
Platform.select() can also be used directly as a value (not just spread inside a style
object) — for example returning a whole different string or number depending on platform, exactly
like the earlier ternary example but easier to extend with a third or fourth case later.
Bigger Differences: Platform-Specific File Extensions
When the difference between platforms isn't just one style value but the ENTIRE implementation of a
component — different libraries, different layout, different behavior — sprinkling
Platform.select() calls throughout the file gets messy. React Native supports a second
approach: giving a component two separate files with a platform suffix in the filename.
// Button.ios.js
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
export default function Button({ title, onPress }) {
return (
<TouchableOpacity onPress={onPress} style={{ padding: 12 }}>
<Text style={{ color: '#007aff' }}>{title}</Text>
</TouchableOpacity>
);
}
// Button.android.js
import React from 'react';
import { TouchableNativeFeedback, View, Text } from 'react-native';
export default function Button({ title, onPress }) {
return (
<TouchableNativeFeedback onPress={onPress}>
<View style={{ padding: 12 }}>
<Text style={{ color: '#1a73e8', fontWeight: 'bold' }}>{title}</Text>
</View>
</TouchableNativeFeedback>
);
}
Elsewhere in the app you simply write import Button from './Button'; — no
Platform.OS check anywhere in the importing code. React Native's bundler automatically
resolves ./Button to Button.ios.js on iOS and Button.android.js
on Android at build time. Use this approach when a component's entire implementation genuinely
diverges between platforms (different underlying native touch feedback, different libraries
entirely) — not just for a single style value, where Platform.select() inside one
shared file is simpler and keeps related code together.
Platform.select() inside the shared component file. A whole different implementation → separate .ios.js / .android.js files.The Classic Gotcha: Shadows vs. Elevation
One of the most common platform differences every React Native developer runs into early is card
shadows. iOS uses a family of four separate style properties — shadowColor,
shadowOffset, shadowOpacity, and shadowRadius — none of which
do anything on Android. Android instead uses a single elevation property, which draws a
native Material Design shadow based on how "raised" the view appears to be. Neither approach works
on the other platform, so a shadow style written for iOS alone will render completely flat on
Android, and vice versa.
import { StyleSheet, Platform } from 'react-native';
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 6,
},
android: {
elevation: 4,
},
}),
},
});
Wrapping both sets of properties in one Platform.select() call keeps the "this is my
card shadow" intent in a single, readable place, even though the two platforms need entirely
different property names to achieve a visually similar effect.
Other Common Platform Differences Worth Knowing
| Area | Typical difference |
|---|---|
| Status bar | Android often needs an explicit background color set via StatusBar; iOS status bar style (light/dark content) is set separately |
| Back navigation | Android has a hardware/gesture back button apps must handle; iOS relies entirely on in-app back buttons or swipe gestures |
| Fonts | Default system fonts differ (San Francisco on iOS, Roboto on Android) unless you load custom fonts |
| Ripple vs. opacity feedback | Android users expect a ripple effect on touch (TouchableNativeFeedback); iOS users expect a fade (TouchableOpacity) |