Aman Mittal 👨‍💻Developer 👉 Nodejs, Reactjs, ReactNative | Tech Blogger with 1M+ views on Medium | My weekly dev newsletter 👉 tinyletter.com/amanhimself

How to use styled-components with React Native

10 min read 3001

How to use Styled Components with React Native

Styling an application is an important aspect of development. Managing styles could lead to better code readability. In React Native apps, the styling of components is done with the default method ofStyleSheet API. However, the CSS-in-JS approach using styled-components is another way to create and style UI components in React Native apps.

In this tutorial, let’s discuss what advantages a library like styled-components has over the general StyleSheet manager in React Native.

What’s the difference between StyleSheet and the styled-components library?

styled-components is a CSS-in-JS library that is open source and allows you, as a React Native developer, to define a UI component and styles in a single file location. It becomes easy to couple styling with its suitable component that may result in an optimized developer experience when working with large applications.

An advantage it has over React Native’s default way of defining styles is that it allows us to use plain CSS over styles that are defined using JavaScript objects. It is better for developers with experience in a web-based background.

For example, React Native follows a styling convention when defining CSS property names. It uses camelCase based property names. A background-color property in React Native app using StyleSheet API is written as:

backgroundColor: 'tomato';

When using the styled-components library, you can use the CSS naming conventions. Thus, backgroundColor is written as background-color just like in plain CSS.

React Native allows us to define values for properties like margin, padding, shadows, font size, and so on by defining without a unit such as px (pixels). For example, the font size when using StyleSheet API is defined as:

fontSize: 20;

These unitless values render differently on various screen sizes due to the pixel density of a particular screen. However, using styled-components you have to use the suffix px when defining the value of a property like font-size. But this does mean that there is a disadvantage caused by this library. Behind the scenes, styled-components converts plain CSS properties into React Native stylesheet objects using a package called css-to-react-native.

Installing styled-components

Start by creating a new React Native project. For a quick development process, I am going to use expo-cli. After the project directory is created, make sure to install the styled-components library. Open up a terminal window and execute the following command:

npx expo-cli init [Your Project Name]
# after the project is created
cd [Your Project Name]
# then install the library
yarn add styled-components@5.2.0
# also install the icons library
expo install @expo/vector-icons

Using styled-components

In this section let’s write our first styled component to display the title of the app in the App.js file. To get started, import the library:

We made a custom demo for .
No really. Click here to check it out.

import styled from 'styled-components/native';

To use the styled-components library in a React Native app, you must import the /native to access primitive components instead of importing them directly from React Native.

Then, let’s replace the View and Text components from the react-native library. These new components are going to follow the custom semantics from styled-components.

Here is an example of View and Text components being styled using styled objects from the styled-components library:

const Container = styled.View`
  flex: 1;
  background-color: white;
  align-items: center;
  justify-content: center;
`;
const Text = styled.Text`
  font-size: 18px;
  color: blue;
  font-weight: 500;
`;

These custom styled-components use the same CSS logic to style the components as any React Native app. The difference here is that writing property names and their values closely follow plain CSS conventions rather than React Native.

The modified App component is going to be:

export default function App() {
  return (
    <Container>
      <Text>Open up App.js to start working on your app!</Text>
    </Container>
  );
}

Now, go back to the terminal window, and to see the code in action, execute the command yarn start. You can now view the following result in any iOS or Android emulator or a real device that has Expo Client app.

expo client app emulator

Using props in styled-components

Creating custom components in any React or React Native app requires the use of props. The advantage here is that the application’s code remains as DRY as possible. The styled-components library does allow consuming props in custom components (something not available in React Native when using StyleSheet objects).

Let’s take a trivial example. In the components/ directory create a new component file called PressableButton.js. This component will be used in the App component later to display a custom button component.

Inside this file, let’s create a custom component using TouchableOpacity and Text. This custom button component is going to have props such as a custom button title, and bgColor to set the background color of the button component to any valid value from the parent component:

import React from 'react';
import styled from 'styled-components/native';
const ButtonContainer = styled.TouchableOpacity`
  margin-vertical: 40px;
  width: 120px;
  height: 40px;
  padding: 12px;
  border-radius: 10px;
  background-color: ${props => props.bgColor};
`;
const ButtonText = styled.Text`
  font-size: 16px;
  text-align: center;
`;
const PressableButton = ({ onPress, bgColor, title }) => (
  <ButtonContainer onPress={onPress} bgColor={bgColor}>
    <ButtonText>{title}</ButtonText>
  </ButtonContainer>
);
export default PressableButton;

To set the background color of this custom component dynamically, you can pass an interpolated function such as ${props => props ...}.

This interpolated function is equivalent to the following code snippet:

background-color: ${(props) => {
  return (props.bgColor)
}}

To test this out, go to the App.js component file, import the PressableButton component after other statements:

import PressableButton from './components/PressableButton';

Then, modify its contents:

export default function App() {
  return (
    <Container>
      <Text>Open up App.js to start working on your app!</Text>
      <PressableButton
        onPress={() => true}
        title='First button'
        bgColor='papayawhip'
      />
    </Container>
  );
}

Here is the output you are going to get after this step:
Open up App.js to start working on your app
Let’s add another button with a different background color:

<PressableButton onPress={() => true} title='First button' bgColor='#4267B2' />

It is displayed after the first button:
one tan button with the words first button one blue button with the words first button

Interpolated functions in the styled-components library can also be used to extend the style of a particular property. For example, the text color on the second button is not looking good. To change that, we want to have two variants of the PressableButton component.

The first one is going to be primary where the color of the text is going to be white. The second variant is going to have a text color of black but is going to act like the default variant. This means that the second variant does not have to be explicitly defined.

Modify the PressableButton.js file by adding a color attribute to the ButtonText styled object:

const ButtonText = styled.Text`
  font-size: 16px;
  text-align: center;
  color: ${props => (props.primary ? 'white' : '#010101')};
`;

Then add the prop primary to the PressableButton component:

const PressableButton = ({ onPress, primary, bgColor, title }) => (
  <ButtonContainer onPress={onPress} bgColor={bgColor}>
    <ButtonText primary={primary}>{title}</ButtonText>
  </ButtonContainer>
);

Now, go back to the App.js file and add the prop variant primary to the second button:

<PressableButton
  onPress={() => true}
  title='First button'
  bgColor='#4267B2'
  primary
/>

The changes are reflected instantly in the Expo Client app:
open up app js

Building an app screen from scratch

Let’s build an app screen to see how we can define different primitive React Native components using styled objects from the styled-components library.

To give you an overview, of what we are going to build, here is the end result:

user 1 and user 2 on screen

If you want to follow along, please download the following assets from here.

Adding a SafeAreaView component

Let’s start by building the header section of the app. Create a new component file called components/Header.js. This component is going to display the app Title as well as two icons in a row. These two icons can be used to create further actions. To display icons let’s use MaterialCommunityIcons from @expo/vector-icons.

This Header component is going to receive the prop headerTitle from the parent component App. The Title is defined from the Text and each icon is wrapped by a TouchableOpacity button called IconButton.

Add the following code snippet:

import React from 'react';
import styled from 'styled-components/native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
const Container = styled.View`
  width: 100%;
  height: 50px;
  padding-horizontal: 10px;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
`;
const Title = styled.Text`
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 0.25px;
  color: #4267b2;
`;
const IconButtonsRow = styled.View`
  flex-direction: row;
`;
const IconButton = styled.TouchableOpacity`
  width: 40px;
  height: 40px;
  border-radius: 20px;
  background: #e6e6e6;
  align-items: center;
  justify-content: center;
  margin-left: 12px;
`;
const Header = ({ headerTitle }) => {
  return (
    <Container>
      <Title>{headerTitle}</Title>
      <IconButtonsRow>
        <IconButton activeOpacity={0.7} onPress={() => true}>
          <MaterialCommunityIcons name='magnify' size={28} color='#010101' />
        </IconButton>
        <IconButton activeOpacity={0.7} onPress={() => true}>
          <MaterialCommunityIcons
            name='facebook-messenger'
            size={28}
            color='#010101'
          />
        </IconButton>
      </IconButtonsRow>
    </Container>
  );
};
export default Header;

Notice in the above code snippet on IconButton that you can use the available prop such as activeOpacity in the same way you would use it on a TouchableOpacity component. styled-components still uses the props that are available on a React Native component.

Next, import this component inside App.js file and modify it:

import React from 'react';
import styled from 'styled-components/native';
import Header from './components/Header';
const Container = styled.View`
  flex: 1;
  background-color: white;
`;
export default function App() {
  return (
    <Container>
      <Header headerTitle='social' />
    </Container>
  );
}

Here is the output you are going to get on an iOS simulator:
words 'social' on iOS emulator

The Header component is taking the space behind the safe area boundaries of an iOS device. To fix this, fortunately, the styled-components library provides a component called SafeAreaView which works in the same manner as React Native’s SafeAreaView.

Modify the following snippet:

const Container = styled.SafeAreaView;

Now the output is the desired result:
the words social displayed on blank screen on iOS simulator

Display an avatar using the Image component

To display an avatar image, the Image component can be used. Create a new component file called components/Avatar.js. It is going to receive one prop that is the source of the image to display. You can use the source prop to reference the image.

The styling for Avatar image begins with a width and a height of 64 pixels. Having a border-radius property that equals exactly half the value of width and height, makes the image a circle. The border-radius property is used to create rounded corners.

Add the following code snippet:

import React from 'react';
import styled from 'styled-components/native';
const Container = styled.View`
  width: 64px;
  height: 64px;
`;
const Image = styled.Image`
  width: 64px;
  height: 64px;
  border-radius: 32px;
`;
const Avatar = ({ imageSource }) => {
  return (
    <Container>
      <Image source={imageSource} />
    </Container>
  );
};
export default Avatar;

Now, open App.js file and import the Avatar component. Wrap it with another component called RowContainer that has the following styles:

// after other import statements
import Avatar from './components/Avatar';
const RowContainer = styled.View`
  width: 100%;
  padding-horizontal: 10px;
  flex-direction: row;
`;

Modify the JSX rendered by App component:

<Container>
  <Header headerTitle='social' />
  <RowContainer>
    <Avatar imageSource={require('./assets/images/avatar1.png')} />
  </RowContainer>
</Container>

The avatar image looks like this:
avatarimagedisplayed

Using position: absolute property to create an online indicator

CSS properties such as margin and padding are available to add spacing between different UI elements. The spacing is in relation to one another. There are some scenarios when this relation is not aligned exactly like the design of the app states. For these scenarios, we often use position: absolute property.

React Native relies on the position: relative property by default. This is the reason we do not have to explicitly define it every time we style the UI components. The absolute value is used only for scenarios to fit the exact design pattern. It comes with the following properties that are often used in combination:

  • top
  • left
  • right
  • bottom

Let’s enhance the Avatar component by displaying an online status indicator on the top right corner of the avatar image.

Define a new custom component called OnlineIndicator. It is going to have a fixed width and height values as well as a border. The good thing about using styled-components is that you can define the value of the border property in the same way as in plain CSS.

The position is set to absolute, which means that the OnlineIndicator component is going to be relative to its parent component. Since it has to be displayed on the top and right corner, let’s set the values of those two properties to 0.

You can modify these values to satisfy the design pattern you are following by increasing or decreasing each value in pixels:

const OnlineIndicator = styled.View`
  background-color: green;
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 8px;
  top: 0;
  right: 0;
  border: 2px solid white;
`;
const Avatar = ({ imageSource }) => {
  return (
    <Container>
      <Image source={imageSource} />
      <OnlineIndicator />
    </Container>
  );
};

Here is the output after this step:
online indicator on avatar

Adding a TextInput

To add a text input component, the styled object uses TextInput. Create a new file called components/InputContainer.js and add the following code snippet:

import React from 'react';
import styled from 'styled-components/native';
const Container = styled.View`
  width: 100%;
`;
const TextInput = styled.TextInput`
  width: 100%;
  height: 60px;
  font-size: 18px;
  flex: 1;
  color: #010101;
  margin-left: 10px;
`;
const InputContainer = () => {
  return (
    <Container>
      <TextInput placeholder="What's on your mind?" />
    </Container>
  );
};
export default InputContainer;

Since we want to display this component next to the avatar image, let’s import it in the App.js file:

<RowContainer>
  <Avatar imageSource={require('./assets/images/avatar1.png')} />
  <InputContainer />
</RowContainer>

Here is the output:
"whats on your mind"

Mapping through a list of menu items using ScrollView

Using a ScrollView component let’s display a list of posts. Each post is going to have a user’s avatar image, user name, post description, and post image. To display the data, I am going to use a mock array that contains four different objects. Add this array in the App.js file:

const DATA = [
  {
    id: '1',
    userAvatar: require('./assets/images/avatar2.png'),
    userName: 'User 1',
    postText:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
    postImage: require('./assets/images/post1.png')
  },
  {
    id: '2',
    userAvatar: require('./assets/images/avatar4.png'),
    userName: 'User 2',
    postText:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
    postImage: require('./assets/images/post2.png')
  },
  {
    id: '3',
    userAvatar: require('./assets/images/avatar3.png'),
    userName: 'User 3',
    postText:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
    postImage: require('./assets/images/post3.png')
  },
  {
    id: '4',
    userAvatar: require('./assets/images/avatar4.png'),
    userName: 'User 4',
    postText:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
    postImage: require('./assets/images/post4.png')
  }
];

Next, create a new component file called components/Card.js. It is going to accept the array of data as the only prop. To iterate through the data array, JavaScript’s map function is used. We already know the structure of data represented.

Start by creating VerticalList which is defined from ScrollView. Then create an empty ListContainer component which is the representation of a View component.

To display the username and avatar image, create a Row container component that has a flex-direction property set to the value of row. The SmallAvatar is going to display the user’s avatar image and UserName is used to display the username. Similarly, the PostDescription and PostImage are used to display the contents of a post.

Add the following snippet:

import React from 'react';
import styled from 'styled-components/native';
const VerticalList = styled.ScrollView`
  flex: 1;
`;
const ListContainer = styled.View``;
const Header = styled.View`
  height: 50px;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  margin-top: 6px;
  padding: 0 11px;
`;
const Row = styled.View`
  align-items: center;
  flex-direction: row;
`;
const SmallAvatar = styled.Image`
  width: 32px;
  height: 32px;
  border-radius: 16px;
`;
const UserName = styled.Text`
  padding-left: 8px;
  font-size: 14px;
  font-weight: bold;
  color: #010101;
`;
const PostDescription = styled.Text`
  font-size: 14px;
  color: #222121;
  line-height: 16px;
  padding: 0 11px;
`;
const PostImage = styled.Image`
  margin-top: 9px;
  width: 100%;
  height: 300px;
`;
const Card = ({ data }) => {
  return (
    <VerticalList showsVerticalScrollIndicator={false}>
      {data.map(item => (
        <ListContainer key={item.id}>
          <Header>
            <Row>
              <SmallAvatar source={item.userAvatar} />
              <UserName>{item.userName}</UserName>
            </Row>
          </Header>
          <PostDescription>{item.postText}</PostDescription>
          <PostImage source={item.postImage} />
        </ListContainer>
      ))}
    </VerticalList>
  );
};
export default Card;

Any available attribute on a React Native component is valid on a component created using styled object. For example, to hide the vertical scroll indicator, set the value of showsVerticalScrollIndicator to false, just as in the above snippet.

To see the ScrollView working, add the Card component in App.js file as shown below:

// import the Card component
import Card from './components/Card';
return (
  <Container>
    {/* rest remains same */}
    <Card data={DATA} />
  </Container>
);

Here is the output:
feed and avatar with status and image cards
You can also extend the styled object with ScrollView by using a chainable method called attrs. It attaches the props to a styled component.

The VerticalList component we created does not require custom props but by using the chainable method, it can ScrollView‘s contentContainerStyle object to apply the style to the scroll view content container. Let’s add some padding vertical of 20 and some background color:

const VerticalList = styled.ScrollView.attrs(props => ({
  contentContainerStyle: {
    backgroundColor: '#e7e7e7',
    paddingVertical: 20
  }
}))`
  flex: 1;
`;

completed ui with avatar, online indicator, card and status
Conclusion

I hope you had fun reading this tutorial. Using the styled-components library with React Native does have its share of advantages as we discussed in this post.

Have you tried styled-components with React Native before? If not, are you going to try it now in your next project? Do comment below.

Source code available a this GitHub repo.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    Aman Mittal 👨‍💻Developer 👉 Nodejs, Reactjs, ReactNative | Tech Blogger with 1M+ views on Medium | My weekly dev newsletter 👉 tinyletter.com/amanhimself

    Leave a Reply