Lesson 13

Modal Screens

Modals slide up from the bottom of the screen and overlay the content behind them. They're used for focused tasks — composing a message, filling out a form, or confirming an action. React Navigation supports modals through the presentation screen option.

Basic Modal

Set presentation: 'modal' on a screen to present it as a modal:

<Stack.Screen
  name="CreatePost"
  component={CreatePostScreen}
  options={{ presentation: 'modal' }}
/>

When you navigate to this screen, it slides up from the bottom instead of the standard right-to-left push. On iOS, the previous screen scales down slightly behind it — matching the native modal behavior.

Modal with a Close Button

Modals typically have a close button instead of a back arrow. Add one with headerLeft:

<Stack.Screen
  name="CreatePost"
  component={CreatePostScreen}
  options={({ navigation }) => ({
    presentation: 'modal',
    title: 'New Post',
    headerLeft: () => (
      <Button title="Cancel" onPress={() => navigation.goBack()} />
    ),
  })}
/>

Transparent Modals

Use presentation: 'transparentModal' for overlays that show the previous screen behind them:

<Stack.Screen
  name="Alert"
  component={AlertScreen}
  options={{ presentation: 'transparentModal', headerShown: false }}
/>

You'll need to style the screen with a semi-transparent background:

function AlertScreen({ navigation }) {
  return (
    <View style={alertStyles.overlay}>
      <View style={alertStyles.modal}>
        <Text style={alertStyles.title}>Are you sure?</Text>
        <Button title="Yes" onPress={() => navigation.goBack()} />
        <Button title="Cancel" onPress={() => navigation.goBack()} />
      </View>
    </View>
  );
}

const alertStyles = StyleSheet.create({
  overlay: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modal: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 24,
    width: '80%',
    alignItems: 'center',
  },
  title: { fontSize: 18, fontWeight: 'bold', marginBottom: 16 },
});

A Group for Modals

A clean pattern is to use a Stack.Group to apply modal presentation to multiple screens at once:

<Stack.Navigator>
  <Stack.Group>
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Details" component={DetailsScreen} />
  </Stack.Group>

  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="CreatePost" component={CreatePostScreen} />
    <Stack.Screen name="EditProfile" component={EditProfileScreen} />
  </Stack.Group>
</Stack.Navigator>

All screens in the second group present as modals. This keeps the modal configuration in one place instead of repeating it on each screen.

Complete Example

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button, TextInput, StyleSheet } from 'react-native';

const Stack = createNativeStackNavigator();

function HomeScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Feed</Text>
      <Button title="New Post" onPress={() => navigation.navigate('CreatePost')} />
    </View>
  );
}

function CreatePostScreen({ navigation }) {
  const [text, setText] = React.useState('');

  return (
    <View style={styles.modalContent}>
      <TextInput
        style={styles.input}
        placeholder="What's on your mind?"
        value={text}
        onChangeText={setText}
        multiline
        autoFocus
      />
      <Button
        title="Post"
        onPress={() => {
          console.log('Posted:', text);
          navigation.goBack();
        }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  title: { fontSize: 24, marginBottom: 16 },
  modalContent: { flex: 1, padding: 16, paddingTop: 24 },
  input: {
    fontSize: 18,
    minHeight: 120,
    textAlignVertical: 'top',
    marginBottom: 16,
  },
});

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen
          name="CreatePost"
          component={CreatePostScreen}
          options={({ navigation }) => ({
            presentation: 'modal',
            title: 'New Post',
            headerLeft: () => (
              <Button title="Cancel" onPress={() => navigation.goBack()} />
            ),
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Tapping "New Post" slides the compose screen up as a modal. The cancel button and swipe-down gesture dismiss it.

In the next lesson we'll set up deep linking to navigate into specific screens from URLs.