Building Stylistic UIs with Emotion-JS for React Native

Published on Jun 4, 2019

15 min read

EXPO

Styling is an important aspect of any mobile application. You cannot put enough emphasis on how important it is for a mobile app to have a pleasing design and good use of colors for the app users to use it in the long term.

If you are into React Native development, by now, you may know that there are different ways to style a React Native application. Methods such as by using StyleSheet object to create to styles for each component screen, or encapsulating all of your styles in one file for the whole application.

Using third-party libraries for styling is another way that can save you a lot of time to develop your React Native application. Some CSS-in-JS libraries such as styled components and emotion-js are already a common practice among web developers. This tutorial is going to discuss and showcase how you can use Emotion-JS in a React Native application.

What is Emotion-JS 👩‍🎤?

🔗

Emotion is a flexible CSS-in-JS library that somehow enforces developers to write each component with their own styles and has both of them in one place. This enforcement has lead to some happy times for some happy developers resulting in optimizing their experience and output. It has predictable composition to avoid specificity issues with CSS.

React Native tends to follow a certain convention when it comes to styling your app. Such as all CSS property names should be in camelCase such as for background-color in React Native is:

1background-color: 'papayawhip';

Developers coming from a web background, do get uncomfortable by these conventions. Using a third party library like emotion-js can give help you. You do not have to switch between the context of conventions, apart from the properties and React Native’s own flexbox rules.

Installing Emotion

🔗

To get started, you need a new React Native project. To quickly scaffold one, let us use the power of Expo. Run the following command to install expo cli and create a new React Native project using the same cli.

# To install expo-cli
npm install -S expo-cli
# Generate a project
expo init rn-emotion-demo

When running the last command, the command line prompt will you a few questions. First one is, Choose a template, where I chose expo-template-blank, then enter display name of your app and then either use npm or yarn to install dependencies. I am going with yarn.

Once all the dependencies installed, you can open this project in your favorite code editor. Next step is to install the latest version of emotion library.

yarn add prop-types @emotion/core @emotion/native

Guidelines in emotion package instructs that you need prop-types as the package installed with your project since Emotion as the package itself depends on it. Next two packages in the above command are necessary to use the library itself. Also, make sure, when you install these dependencies, you are inside the React Native project directory.

Writing your first Emotion Style

🔗

To start the application in a simulator, run expo start. On successfully starting the application, you will be prompted with the default Expo app. Modify the App.js file like below to get started. Make changes to the component’s render function like below. Replace both View and Text with Container and Title. These new elements are going to be custom using semantics from emotion.

1export default class App extends React.Component {
2 render() {
3 return (
4 <Container>
5 <Title>React Native with 👩‍🎤 Emotion</Title>
6 </Container>
7 );
8 }
9}

Emotion-JS utilizes tagged template literals to style your components using back-ticks. When creating a component in React or React Native using this styling library, each component is going to have styles attached to it. Define the below styles in the same file after the App component.

1const Container = styled.View`
2 flex: 1;
3 background-color: papayawhip;
4 justify-content: center;
5 align-items: center;
6`;
7
8const Title = styled.Text`
9 font-size: 20px;
10 font-weight: 500;
11 color: palevioletred;
12`;

Notice the Container is a React Native View and has styling attached to it. Similarly, Title is utilizing Text component from React Native. You will get the following result.

Here is the complete code for App.js file.

1import React from 'react';
2import { StyleSheet, Text, View } from 'react-native';
3import styled, { css } from '@emotion/native';
4
5export default class App extends React.Component {
6 render() {
7 return (
8 <Container>
9 <Title>React Native with 👩‍🎤 Emotion</Title>
10 </Container>
11 );
12 }
13}
14
15const Container = styled.View`
16 flex: 1;
17 background-color: papayawhip;
18 justify-content: center;
19 align-items: center;
20`;
21
22const Title = styled.Text`
23 font-size: 24px;
24 font-weight: 500;
25 color: palevioletred;
26`;

In the above snippet, do take a note that we are not importing a React Native core components such as View, Text or StyleSheet object. It is that simple. It uses the same flexbox model that React Native Layouts use. The advantage here is that you get to apply almost similar context and understandable syntax that you have been using in Web Development to style a React Native application.

Using Props in Emotion-JS

🔗

Often you will find yourself creating custom components for your apps. This does give you the advantage to stay DRY. Leveraging emotion-js is no different. You can use this programming pattern by building custom components that require their parent components. props are commonly known as additional properties to a specific component. To demonstrate this, create a new file called CustomButton.js.

Inside this file, we are going to create a custom button that requires props such as backgroundColor, textColor and the text itself for the title of the button. You are going to use TouchableOpacity and Text to create this custom button but without importing react-native library and create a functional component CustomButton.

1import React from 'react';
2import styled, { css } from '@emotion/native';
3
4const CustomButton = props => (
5 <ButtonContainer
6 onPress={() => alert('You are using Emotion-JS!')}
7 backgroundColor={props.backgroundColor}
8 >
9 <ButtonText textColor={props.textColor}>{props.text}</ButtonText>
10 </ButtonContainer>
11);
12
13export default CustomButton;
14
15const ButtonContainer = styled.TouchableOpacity`
16 margin: 15px;
17 width: 100px;
18 height: 40px
19 padding: 12px;
20 border-radius: 10px;
21 background-color: ${props => props.backgroundColor};
22`;
23
24const ButtonText = styled.Text`
25 font-size: 15px;
26 color: ${props => props.textColor};
27 text-align: center;
28`;

The important thing to notice in the above snippet is you can pass an interpolated function ${props => props...} to an emotion-js template literal to extend it the component's style and keep the component re-usable.

To see this custom button in action, import it to the App.js file as below.

1// ... other imports
2import CustomButton from './components/CustomButton';
3
4// ...
5
6export default class App extends React.Component {
7 render() {
8 return (
9 <Container>
10 <Title>React Native with 👩‍🎤 Emotion</Title>
11 <CustomButton
12 text="Click Me"
13 textColor="#01d1e5"
14 backgroundColor="lavenderblush"
15 />
16 </Container>
17 );
18 }
19}

On running the simulator, you will get the following result.

Inline Styling

🔗

As React Native developer, you have used inline styling and you know how beneficial they can be, especially in the prototypal stage of an application.

To leverage this technique using Emotion-js, open CustomButton.js file and add an inline style like below. Do note that, we are not modifying the existing styles defined previously.

1const CustomButton = props => (
2 <ButtonContainer
3 onPress={() => alert("You are using Emotion-JS!")}
4 backgroundColor={props.backgroundColor}
5 style={css`
6 border-width: 1px;
7 `}
8 >
9 <ButtonText textColor={props.textColor}>{props.text}</ButtonText>
10 </ButtonContainer>

The style tag in the above snippet uses css prop from @emotion/native library to allow us to add inline styles.

Building the Grocery UI

🔗

Onwards this section, you are going to use what you have just learned about Emotion-js by building a better UI in terms of complexity than a simple test and a button.

Open up App.js. Declare a new ContainerView using styled prop from emotion-js. Inside the backticks, you can put pure CSS code there with the exact same syntax. The View element is like a div in HTML or web programming in general. Also, create another view called Titlebar inside Container.

Inside Titlebar, it will contain three new elements. One is going to be an image Avatar and the other two are text: Title and Name.

1import React from 'react';
2import styled, { css } from '@emotion/native';
3
4export default class App extends React.Component {
5 render() {
6 return (
7 <Container>
8 <Titlebar>
9 <Avatar />
10 <Title>Welcome back,</Title>
11 <Name>Aman</Name>
12 </Titlebar>
13 </Container>
14 );
15 }
16}
17
18const Container = styled.View`
19 flex: 1;
20 background-color: white;
21 justify-content: center;
22 align-items: center;
23`;
24
25const Titlebar = styled.View`
26 width: 100%;
27 margin-top: 50px;
28 padding-left: 80px;
29`;
30
31const Avatar = styled.Image``;
32
33const Title = styled.Text`
34 font-size: 20px;
35 font-weight: 500;
36 color: #b8bece;
37`;
38
39const Name = styled.Text`
40 font-size: 20px;
41 color: #333333;
42 font-weight: bold;
43`;

You will get the following result in the simulator.

Right now, everything is how in the middle of the screen. We need the Titlebar and its contents at the top of the mobile screen. So styles for Container can be modified as below.

1const Container = styled.View`
2 flex: 1;
3 background-color: white;
4`;

You will get the following result.

Adding the user avatar image

🔗

I am going to use an image that is stored in the assets folder in the root of our project. If are free to use your own image but you can also download the assets for this project below.

  • rn-emotion-demo

To create an image even with emotion-js, you need the Image component from React Native core. You can use the source props to reference the image based on where it is located.

1<Titlebar>
2 <Avatar source={require('./assets/avatar.jpg')} />
3 <Title>Welcome back,</Title>
4 <Name>Aman</Name>
5</Titlebar>

The styling for Avatar will begin with a width and a height each of 44 pixels.

1const Avatar = styled.Image`
2 width: 44px;
3 height: 44px;
4`;

You will get the following result.

Absolute Positioning in React Native

🔗

Now notice that the avatar image and the text are piling up. They are taking the same space on the screen. To avoid this, you are going to use position: absolute CSS property.

CSS properties such as padding and margin are used to add space between UI elements in relation to one another. This is the default layout position. However, you are currently in a scenario where it will be beneficial to use absolute positioning of UI elements and place the desired UI element at the exact position you want.

In React Native and CSS in general, if position property is set to absolute, then the element is laid out relative to its parent. CSS has other values for position but React Native only supports absolute.

Modify Avatar styles as below.

1const Avatar = styled.Image`
2 width: 44px;
3 height: 44px;
4 margin-left: 20px;
5 position: absolute;
6 top: 0;
7 left: 0;
8`;

Usually, with position absolute property, you are going to use a combination of the following properties:

  • top
  • left
  • right
  • bottom

In the case above, we use top and left and both are set to 0 pixels. You will get the following output.

Mapping through a list of categories

🔗

Inside components/ folder create a new file called Categories.js. This file is going to render a list of category items for the Grocery UI app.

1import React from 'react';
2import styled, { css } from '@emotion/native';
3
4const Categories = props => (
5 <Container>
6 <Name>Fruits</Name>
7 <Name>Bread</Name>
8 <Name>Drinks</Name>
9 <Name>Veggies</Name>
10 </Container>
11);
12
13export default Categories;
14
15const Container = styled.View``;
16
17const Name = styled.Text`
18 font-size: 28px;
19 font-weight: 600;
20 margin-left: 15px;
21 color: #bcbece;
22`;

All the data is static right now. Import this component in App.js and place it after Titlebar.

1<Container>
2 <Titlebar>
3 <Avatar source={require('./assets/avatar.jpg')} />
4 <Title>Welcome back,</Title>
5 <Name>Aman</Name>
6 </Titlebar>
7 <Categories />
8</Container>

You will get the following result.

There can be a number of categories. To make the names of categories dynamic, we can send it through App.js file.

1const items = [
2 { text: 'Fruits' },
3 { text: 'Bread' },
4 { text: 'Drinks' },
5 { text: 'Veggies' },
6 { text: 'Meat' },
7 { text: 'Paper Goods' }
8];
9
10// ...
11
12// Inside the render function replace <Categories /> with
13
14{
15 items.map((category, index) => (
16 <Categories name={category.text} key={index} />
17 ));
18}

In the above snippet, you are using map function from JavaScript to iterate through an array render a list of items, in this category names. Adding a key prop is required. To make this work, also modify Categories.js.

1import React from 'react';
2import styled, { css } from '@emotion/native';
3
4const Categories = props => <Name>{props.name}</Name>;
5
6export default Categories;
7
8const Name = styled.Text`
9 font-size: 28px;
10 font-weight: 600;
11 margin-left: 15px;
12 color: #bcbece;
13`;

There is no change in the UI.

Adding Horizontal ScrollView

🔗

This list is right now not scrollable. To make it scrollable, let us place it inside a ScrollView. Open up App.js file place the categories inside a ScrollView, but first, import it from React Native core.

1import { ScrollView } from 'react-native';
2
3//...
4<ScrollView>
5 {items.map((category, index) => (
6 <Categories name={category.text} key={index} />
7 ))}
8</ScrollView>;

You will notice not a single change in the UI. By default, scrollable lists in React Native using ScrollView are vertical. Make this horizontal by adding the prop horizontal.

1<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
2 {items.map((category, index) => (
3 <Categories name={category.text} key={index} />
4 ))}
5</ScrollView>

It works.

To make it appear better, add some inline styling using css prop.

1<ScrollView
2 horizontal={true}
3 showsHorizontalScrollIndicator={false}
4 style={css`
5 margin: 20px;
6 margin-left: 12px;
7 `}
8>

Now it looks better.

Adding a vertical ScrollView

🔗

Next step is to add a ScrollView that acts as a wrapper inside the Container view such that the whole area becomes scrollable vertically. There is a reason to do this. You are now going to add items separated into two columns as images with texts related to a particular category.

Modify App.js file.

1return (
2 <Container>
3 <ScrollView>
4 <Titlebar>{/* and its contents */}</Titlebar>
5 <ScrollView horizontal={true}>
6 {/* Categories being rendered */}
7 </ScrollView>
8 <Subtitle>Items</Subtitle>
9 </ScrollView>
10 </Container>
11);

Notice that we are adding another emotion component called Subtitle which is nothing but a text.

It renders like below.

Building a card component

🔗

In this section, we are going to create a card component that will hold an item’s image, the name of the item and the price as text. Each card component is going to have curved borders and box shadow. This is how it is going to look like.

Create a new component file called Card.js inside the components directory. The structure of the Card component is going to be.

1import React from 'react';
2import styled, { css } from '@emotion/native';
3
4const Card = props => (
5 <Container>
6 <Cover>
7 <Image source={require('../assets/pepper.jpg')} />
8 </Cover>
9 <Content>
10 <Title>Pepper</Title>
11 <PriceCaption>$ 2.99 each</PriceCaption>
12 </Content>
13 </Container>
14);
15
16export default Card;

Currently, it has static data, such as the image, title, and content. Let us add the styles for each styled UI elements in this file.

1const Container = styled.View`
2 background: #fff;
3 height: 200px;
4 width: 150px;
5 border-radius: 14px;
6 margin: 18px;
7 margin-top: 20px;
8 box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
9`;
10
11const Cover = styled.View`
12 width: 100%;
13 height: 120px;
14 border-top-left-radius: 14px;
15 border-top-right-radius: 14px;
16 overflow: hidden;
17`;
18
19const Image = styled.Image`
20 width: 100%;
21 height: 100%;
22`;
23
24const Content = styled.View`
25 padding-top: 10px;
26 flex-direction: column;
27 align-items: center;
28 height: 60px;
29`;
30
31const Title = styled.Text`
32 color: #3c4560;
33 font-size: 20px;
34 font-weight: 600;
35`;
36
37const PriceCaption = styled.Text`
38 color: #b8b3c3;
39 font-size: 15px;
40 font-weight: 600;
41 margin-top: 4px;
42`;

The Container view has a default background of white color. This is useful in scenarios where you are fetching images from a third party APIs. Also, it provides a background to the text area below the image.

Inside the Container view, add an Image and wrap it inside a Cover view. In React Native there two ways you can fetch an image.

If you are getting an image from the static resource as in our case, you use source prop with the keyword require that contains the relative path to the image asset stored in the project folder. In case of networking images or getting an image from an API, you use the same prop with a different keyword called uri. Here is an example of an image being fetched from an API.

1<Image
2 source={{
3 uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png'
4 }}
5/>

The Cover view uses rounded corners with overflow property. This is done to reflect the rounded corners. iOS clips the images if coming from a child component. In our case, the image is coming from a Card component which is a child to the App component.

The Image component takes the width and height of the entire Cover view.

Now let us import this component inside App.js file, after the Subtitle and let us see what results do we get.

1render() {
2 return (
3 <Container>
4 <ScrollView>
5 {/* ... */}
6 <Subtitle>Items</Subtitle>
7 <ItemsLayout>
8 <ColumnOne>
9 <Card />
10 </ColumnOne>
11 <ColumnTwo>
12 <Card />
13 </ColumnTwo>
14 </ItemsLayout>
15 </ScrollView>
16 </Container>
17 )
18}
19
20// ...
21
22const ItemsLayout = styled.View`
23 flex-direction: row;
24 flex: 1;
25`;
26
27const ColumnOne = styled.View``;
28
29const ColumnTwo = styled.View``;

After Subtitle adds a new view called ItemsLayout. This is going to be a layout that allows different cards to be divided between two columns in each row. This can be done by giving this view a flex-direction property of value row. ColumnOne and ColumnTwo are two empty views.

On rendering the final result, it looks like below.

Conclusion

🔗

You have completed the tutorial for creating UIs with Emotion-JS and integrate it into a React Native and Expo application. Now go ahead and create those beautiful UIs for your applications.

Originally published at Heartbeat


More Posts

Browse all posts

Aman Mittal author

I'm a software developer and a technical writer. On this blog, I write about my learnings in software development and technical writing.

Currently, working maintaining docs at 𝝠 Expo. Read more about me on the About page.


Copyright ©  2019-2024 Aman Mittal · All Rights Reserved.