This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Build a screen
Edit page
In this tutorial, learn how to use components such as React Native's Pressable, Expo Image, and Expo UI to build a screen.
In this chapter, we'll create the first screen of the StickerSmash app.
The screen above displays an image and two buttons. The app user can select an image using one of the two buttons. The first button allows the user to select an image from their device. The second button allows the user to continue with a default image provided by the app.
Once the user selects an image, they can add a sticker to it. So, let's start creating this screen.

Build the StickerSmash app's first screen using Pressable, Expo Image, and other core components to create an image picker layout.
1
Break down the screen
Before we build this screen by writing code, let's break it down into some essential elements.
There are two essential elements:
- A large image in the top half of the screen
- Two buttons in the bottom half of the screen
The first button contains multiple components. It has a yellow background. It contains an icon and a text component inside a row.
Now that we've broken down the UI into smaller chunks, we're ready to start coding.
2
Display the image
We'll use expo-image library to display the image in the app. It provides a cross-platform <Image> component to load and render an image. It is already included in the default project template we're using.
The Image component takes the source of an image as its value. The source can be either a static asset or a URL. For example, the source required from assets/images directory is static. It can also come from Network as a uri property.
To use the Image component in src/app/(tabs)/index.tsx file:
- Import
Imagefrom theexpo-imagelibrary. - Create a
PlaceholderImagevariable to use assets/images/background-image.png file as thesourceprop on theImagecomponent. - Replace the outer
ViewwithSafeAreaViewfromreact-native-safe-area-context. Native tabs don't add a header, soSafeAreaViewkeeps the screen content below the status bar on Android and iOS. The default template includes this library. - Add a
paddingTopto the image container withPlatform.select(). On web, the tab list sits at the top of the page, so the image needs more space there.
3
Divide components into files
Let's divide the code into multiple files as we add more components to this screen. Throughout this tutorial, we'll use the components directory to create custom components.
- Create a components directory inside src, and inside it, create the image-viewer.tsx file.
- Move the code to display the image in this file along with the
imagestyles.
Since ImageViewer is a custom component, we are placing it in a separate directory instead of the src/app directory. Every file inside src/app directory is either a layout file or a route file. For more information, see Non-navigation components live outside the src/app directory.
Import ImageViewer and use it in the src/app/(tabs)/index.tsx:
What is the @ in import statement?
The @ symbol is a custom path alias for importing custom components and other modules instead of relative paths. Expo CLI automatically configures it in tsconfig.json.
4
Create buttons using Pressable
React Native includes a few different components for handling touch events, but <Pressable> is recommended for its flexibility. It can detect single taps, long presses, trigger separate events when the button is pushed in and released, and more.
In the design, there are two buttons we need to create. Each has a different style and label. Let's start by creating a reusable component for these buttons. Create a button.tsx file inside the src/components directory with the following code:
The onPress prop lets the screen decide what happens when the user presses the button. Let's import this component into the src/app/(tabs)/index.tsx file. Pass each button an onPress function that calls alert(). Then, add styles for the <View> that encapsulates the buttons:
The app displays an alert when the user taps any of the buttons on the screen. Let's take a look at our app on Android, iOS, and web:
The second button with the label "Use this photo" resembles the actual button from the design. However, the first button needs more styling to match the design.
5
Enhance the reusable button component
The "Choose a photo" button requires different styling than the "Use this photo" button. Let's add a theme prop to apply a primary theme to it. This button also has an icon before the label. We will use the SymbolView component from expo-symbols to display it. On Android and web, SymbolView displays a Material Symbols icon. On iOS, it displays an SF Symbols icon. The default template already includes expo-symbols.
Modify src/components/button.tsx to add the following code snippet:
Let's learn what the above code does:
- The primary theme button passes an array to the
styleprop of<Pressable>. React Native applies the styles in order, so thebackgroundColorfromstyles.primaryButtonapplies only to the primary button. - The label of the primary button uses an inline style, an object passed directly in the
styleprop. It changes the label color to#25292eand overrides thecolorfromstyles.buttonLabel. - The
nameprop ofSymbolViewtakes an icon name for each platform:imagefor Android and web, andphotofor iOS.
Now, modify the src/app/(tabs)/index.tsx file to use the theme="primary" prop on the first button.
Let's take a look at our app on Android, iOS, and web:
6
Use native buttons with Expo UI
Our button uses React Native components, so it looks the same on every platform. Expo UI provides native components: Jetpack Compose views on Android and SwiftUI views on iOS. The default template already includes Expo UI (@expo/ui). Expo UI works in Expo Go.
Let's replace the button on Android and iOS with a native button, and keep the React Native button for web. To do this, we'll use platform-specific extensions. When button.android.tsx and button.ios.tsx exist next to button.tsx, Metro uses the file for the current platform when we import @/components/button. Web still uses button.tsx. For more information, see Platform-specific extensions.
The Android button displays an icon from @expo/material-symbols, a set of Material Symbols icons for Jetpack Compose. To install it, stop the development server by pressing Ctrl + C in the terminal, then run the following command:
Keep the development server stopped until both new files exist. If you add them while the server is running, Metro can keep using button.tsx. In that case, restart the server.
Create a button.android.tsx file inside the src/components directory:
What is Host?
Host is the bridge between React Native and Expo UI. Jetpack Compose components on Android and SwiftUI components on iOS render only inside a Host. The matchContents prop sizes the Host to fit its content, which is the button here. For more information, see Host for Jetpack Compose and SwiftUI.
Then, create a button.ios.tsx file inside the src/components directory:
What are modifiers?
Modifiers customize the appearance and behavior of Expo UI components, such as their style, size, and colors. Import them from @expo/ui/jetpack-compose/modifiers for Jetpack Compose and from @expo/ui/swift-ui/modifiers for SwiftUI. Then, pass them in an array to the modifiers prop of a component, as button.ios.tsx does. For more information, see modifiers for Jetpack Compose and SwiftUI.
Both files keep the same props as button.tsx, so src/app/(tabs)/index.tsx doesn't change. The src/components directory now contains three versions of the button:
srccomponentsbutton.android.tsxUsed on Androidbutton.ios.tsxUsed on iOSbutton.tsxUsed on webimage-viewer.tsxStart the development server again by running npx expo start. Let's take a look at our app on Android, iOS, and web:
Summary
Chapter 3: Build a screen
We've successfully built our app's first screen with Expo Image and native buttons from Expo UI.
In the next chapter, we'll add the functionality to pick an image from the device's media library.