This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Create a modal
Edit page
In this tutorial, learn how to create an emoji picker with the Expo UI BottomSheet component and show the selected emoji as a sticker in a React Native app.
A modal presents content above the rest of the app. In general, modals are used to draw a user's attention toward critical information or guide them to take action. A bottom sheet is a type of modal that slides up from the bottom of the screen.
In this chapter, we'll create an emoji picker with the BottomSheet component from Expo UI. On Android and iOS, it displays the platform's native bottom sheet. On web, it displays a sheet at the bottom of the page.

Build a modal component using React Native's Modal API to display an emoji picker and handle user interactions.
1
Declare a state variable to show buttons
Before implementing the bottom sheet, we are going to add three new buttons. These buttons are visible after the user picks an image from the media library or uses the placeholder image. One of these buttons will open the emoji picker.
In src/app/(tabs)/index.tsx:
- Declare a boolean state variable,
showAppOptions, to show or hide the buttons that open the emoji picker, alongside a few other options. When the app screen loads, we'll set it tofalseso the options are not shown before picking an image. When the user picks an image or uses the placeholder image, we'll set it totrue. - Update the
pickImageAsync()function to set the value ofshowAppOptionstotrueafter the user picks an image. - In the
onPressprop of the button with no theme, replace thealert()with a function that setsshowAppOptionstotrue.
In the above snippet, we're rendering the Button component based on the value of showAppOptions and moving the buttons in the ternary operator block. When the value of showAppOptions is true, render an empty <View> component. We'll address this state in the next step.
2
Add buttons
Let's break down the layout of the option buttons we'll implement in this chapter. The design looks like this:
It contains a parent <View> with three buttons aligned in a row. The button in the middle with the plus icon (+) will open the emoji picker and is styled differently than the other two buttons.
Like the Button component in the third chapter, the option buttons use Expo UI on Android and iOS, and React Native components on web. Each button has one file for each platform.
Inside the src/components directory, create three files for the circle button:
Let's learn what the above code does:
- On Android,
FloatingActionButtondisplays a Material 3 floating action button. Thesizeandclipmodifiers make it a circle of 84 dp, andIcondisplays the add.xml icon from@expo/material-symbols. - On iOS,
Buttonuses theplainstyle, and its label is an SF SymbolsImage. Theframeandbackgroundmodifiers draw a yellow circle of 84 points around the icon. - On web, circle-button.tsx uses
PressableandSymbolView, like button.tsx in the third chapter.
The other two buttons display an icon above a text label. Inside the src/components directory, create three files for the icon button. This component accepts three props:
icon: an icon for each platform. Android uses an XML icon from@expo/material-symbols, iOS uses an SF Symbols name, and web uses a Material Symbols name.label: the text label displayed on the button.onPress: this function invokes when the user presses the button.
On Android, a Column places the Icon above the Text. On iOS, a VStack does the same. All three files accept the same props, like the Button files in the third chapter.
The src/components directory now contains three files for each button:
srccomponentsbutton.android.tsxUsed on Androidbutton.ios.tsxUsed on iOSbutton.tsxUsed on webcircle-button.android.tsxUsed on Androidcircle-button.ios.tsxUsed on iOScircle-button.tsxUsed on webicon-button.android.tsxUsed on Androidicon-button.ios.tsxUsed on iOSicon-button.tsxUsed on webimage-viewer.tsxInside src/app/(tabs)/index.tsx:
- Import the
CircleButtonandIconButtoncomponents to display them. - Add three placeholder functions for these buttons. The
onReset()function invokes when the user presses the reset button, causing the image picker button to appear again. We'll add the functionality for the other two functions later. - Pass an icon for each platform to the
iconprop of eachIconButton.
The option buttons use the same footerContainer style as the Choose a photo and Use this photo buttons. They appear in the same place on the screen, above the tab bar.
Let's take a look at our app on Android, iOS, and web:
3
Create an emoji picker with a bottom sheet
The bottom sheet allows the user to choose an emoji from a list of available emoji. Create an emoji-picker.tsx file inside the src/components directory. This component accepts three props:
isVisible: a boolean to determine whether the bottom sheet is visible.onClose: a function to close the bottom sheet.children: used later to display a list of emoji.
Let's learn what the above code does:
- The
BottomSheetcomponent comes from@expo/ui, which the default template already includes. On Android, it displays a Material 3 bottom sheet. On iOS, it displays a SwiftUI sheet. On web, it displays a sheet at the bottom of the page. - Its
isPresentedprop takes the value ofisVisibleand controls whether the bottom sheet is open or closed. - Its
onDismissprop callsonClosewhen the user closes the bottom sheet. The user can swipe the bottom sheet down or tap outside it. On Android, the back button also closes it. On web, the Esc key also closes it. - Its
snapPointsprop sets the heights where the bottom sheet can rest. The'half'value opens it at about half of the screen height. - Its
containerColorprop sets the background color of the bottom sheet to match the app's background. - The
RNHostViewcomponent displays React Native views, such as<View>and<Text>, inside the bottom sheet.
What is RNHostView?
On Android and iOS, BottomSheet is a native view from Jetpack Compose and SwiftUI. To display React Native views inside it, wrap them in RNHostView. It works in the opposite direction of Host, which we used in the third chapter to display Expo UI components inside React Native views. BottomSheet creates its own Host, so we don't wrap it in one. For more information, see RNHostView.
Now, let's modify the src/app/(tabs)/index.tsx:
- Import the
<EmojiPicker>component. - Create an
isModalVisiblestate variable with theuseStatehook. Its default value isfalse, which hides the bottom sheet until the user presses the button to open it. - Replace the comment in the
onAddSticker()function to update theisModalVisiblevariable totruewhen the user presses the button. This will open the emoji picker. - Create the
onModalClose()function to update theisModalVisiblestate variable. - Place the
<EmojiPicker>component at the bottom of theIndexcomponent.
Here is the result after this step:
4
Display a list of emoji
Let's add a grid of emoji to the bottom sheet's content. We'll use the <FlatList> component from React Native for it.
Create an emoji-list.tsx file inside the src/components directory and add the following code:
Let's learn what the above code does:
- The
emojiarray holds the emoji images. It is outside the component because its value never changes. - The
<FlatList>component takes theemojiarray as the value of thedataprop. TherenderItemprop takes each item from thedataand returns anImagecomponent, wrapped by a<Pressable>. When the user presses an emoji,onSelectreceives it, andonCloseModalcloses the bottom sheet. - The
numColumnsprop displays the list as a grid with three columns. ThecolumnWrapperStyleprop styles each row of the grid.
Now, update the src/app/(tabs)/index.tsx to import the <EmojiList> component and replace the comments inside the <EmojiPicker> component with the following code snippet:
In the EmojiList component, the onSelect prop selects the emoji and after selecting it, the onCloseModal closes the bottom sheet.
Let's take a look at our app on Android, iOS, and web:
5
Display the selected emoji
Now, we'll put the emoji sticker on the image. Create a new file in the src/components directory and call it emoji-sticker.tsx. Then, add the following code:
This component receives two props:
imageSize: a value defined inside theIndexcomponent. We will use this value in the next chapter to scale the image's size when tapped.stickerSource: the source of the selected emoji image.
Import this component in the src/app/(tabs)/index.tsx file and update the Index component to display the emoji sticker on the image. We will check if the pickedEmoji state is not undefined:
Let's take a look at our app on Android, iOS, and web:
Summary
Chapter 5: Create a modal
We've successfully created an emoji picker with a bottom sheet and native buttons from Expo UI, and implemented the logic to display the selected emoji over the image.
In the next chapter, let's add user interactions with gestures to drag the emoji and scale the size by tapping it.