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.

Rule of thumb: one differing style value or number → 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

AreaTypical difference
Status barAndroid often needs an explicit background color set via StatusBar; iOS status bar style (light/dark content) is set separately
Back navigationAndroid has a hardware/gesture back button apps must handle; iOS relies entirely on in-app back buttons or swipe gestures
FontsDefault system fonts differ (San Francisco on iOS, Roboto on Android) unless you load custom fonts
Ripple vs. opacity feedbackAndroid users expect a ripple effect on touch (TouchableNativeFeedback); iOS users expect a fade (TouchableOpacity)
Note: None of this code runs live on this page — this site doesn't build a React Native app. Copy these patterns into your own project (from Installation & Setup) to see them behave differently on an actual iOS simulator and Android emulator.
Next up: most device features you'll ever need — camera, GPS, notifications — don't require any of this platform-branching at all, because a well-maintained package already handles it for you. Continue to Native Modules & Device APIs.