Neo Ighodaro Neo Ighodaro is a twenty-something-year-old fullstack web developer and graphic designer not based in Lagos, Nigeria.

Styling in React: 4 ways to style a React app

8 min read 2503

styling-react-components

Editor’s note: This post was updated in April 2021 to include a section on React styling with Tailwind CSS, updated examples, and additional relevant information.

There may not be many styling in React tutorials out there, but I’ll tell you what: this is the best one. Why, you ask? First, because we’re going to review three methods of styling React components. Second, because I say so (and my editor says I can take artistic liberties).

I take it from your reading this that you’re here to learn about styling React components. When you’re finished reading this post, I guarantee you’ll know how to do just that.

How to style React components

In this article, we are going to see various ways we can style React components. The methods of styling we will explore are:

  • Inline styling
  • styled-components
  • CSS Modules

We will be using a component that is part of a to-do application to explain each of these methods.

If you are new to React, you can check the official documentation to get started.

Setting up your React application

To set up an application, you can use create-react-app. It is the easiest way to get started with a React project. A CRA demo is out of the scope of the article, though, so we’ll skip that and style a make-believe to-do application.

Let’s get started with the first method.

Method #1: Inline styling of React components

If you are familiar with basic HTML, you’ll know that it is possible to add your CSS inline. This is similar in React.

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

We can add inline styles to any React component we want to render. These styles are written as attributes and are passed to the element. Let’s style parts of our component using inline styles:

function ToDoApp() {
 //...
  
  return (
    <div style={{ backgroundColor: "#44014C", width: "300px", minHeight: "200px"}}>
      <h2 style={{ padding: "10px 20px", textAlign: "center", color: "white"}}>ToDo</h2>
      <div>
        <Input onChange={this.handleChange} />
        <p>{this.state.error}</p>
        <ToDoList value={this.state.display} />
      </div>
    </div>
  );
}

We just added inline styles to the outermost div and h2. Here are some things you should note about this.

First, there are two curly brackets. What we are rendering is written in JSX, and, for pure JavaScript expressions to be used in JSX, they have to be included in curly brackets.

The first curly bracket injects JavaScript into JSX. The inner curly brackets creates an object literal. The styles are passed as object literals to the element.

💡 JSX is a preprocessor step that adds XML syntax to JavaScript. You can definitely use React without JSX, but JSX makes React a lot more elegant. Just like XML, JSX tags have a tag name, attributes, and children.

The next thing to note is that the properties are separated by a comma. This is because what we are passing is an object. Because it is a JavaScript attribute, the attributes are written in camelCase and not separated by a dashes.

Now, in the code above, we just added a few properties to the elements we styled. However, imagine we had to add more and more styles to the element. This is where the inline method breaks down because it will not look clean.

There is a way around this, though. We can create object variables and pass them to the elements. Let us do that then.

Creating a style object variable

We create a style object variable the same way we create a JavaScript object. This object is then passed to the style attribute of the element we want to style.

So instead of adding the styles inline directly, as we did in the previous example, we just pass the object variables:

const TodoComponent = {
 width: "300px",
 margin: "30px auto",
 backgroundColor: "#44014C",
 minHeight: "200px",
 boxSizing: "border-box"
}
 
const Header = {
 padding: "10px 20px",
 textAlign: "center",
 color: "white",
 fontSize: "22px"
}
 
const ErrorMessage = {
 color: "white",
 fontSize: "13px"
}
 
 
function ToDoApp() {
 //...
 
 return (
   <div style={TodoComponent}>
     <h2 style={Header}>ToDo</h2>
     <div>
       <Input onChange={this.handleChange} />
       <p style={ErrorMessage}>{this.state.error}</p>
       <ToDoList value={this.state.display} />
     </div>
   </div>
 );
}

In the code above, we created three object variables: TodoComponent, Header, and ErrorMessage. We are then passing these variables to the element instead of typing them directly.

💡 We did not have to use double curly brackets in the element because these variables are objects themselves.

If you look at the object properties, the camelCases will be converted to dash-separated CSS attributes during compilation. For instance, this:

backgroundColor: "#44014C",
minHeight: "200px",
boxSizing: "border-box"

In plain CSS, these will be written as:

background-color: #44014C;
min-height: 200px;
box-sizing: border-box;

⚠️ The camelCase to dash-separated string change applies only to the property names and not property values.

It is possible to pass a variable as a value to a property. So, we can do this:

const spacing = "10px 20px";
const Header = {
  margin: spacing,
  padding: spacing
  // ...
}

In many JavaScript environments, creating a global object variable may be bad practice, but it’s fine in React. Because files are not visible to other files unless they are imported, we can create as many object variables even with the same name without having any conflict.

Sharing styles across many React components

The style objects and the components do not have to be in the same file. We can create a separate .js file for our styles, export these styles, and then import them into the component where we want to use them. Doing this makes styles reusable across multiple components. Let’s do this for our component.

First, we’ll create a separate .js file called styles.js. Then, add these styles:

const TodoComponent = {
  width: "300px",
  margin: "30px auto",
  backgroundColor: "#44014C",
  minHeight: "200px",
  boxSizing: "border-box"
}

const Header = {
  padding: "10px 20px",
  textAlign: "center",
  color: "white",
  fontSize: "22px"
}

const ErrorMessage = {
  color: "white",
  fontSize: "13px"
}

const styles = {
  TodoComponent: TodoComponent,
  Header: Header,
  ErrorMessage: ErrorMessage
}

In the code above, we can choose to export each style object individually, but that will mean importing them individually, too. That might get tedious if there are many style objects in the file.

Therefore, creating an object that contains all styles only makes sense. This object is exported and imported once to the component where it will be used. So let’s do that.

// Import the styles
import {styles} from "./styles";
 
 
function ToDoApp() {
 //...
 
 return (
   <div style={styles.TodoComponent}>
     <h2 style={styles.Header}>ToDo</h2>
     <div>
       <Input onChange={this.handleChange} />
       <p style={styles.ErrorMessage}>{this.state.error}</p>
       <ToDoList value={this.state.display} />
     </div>
   </div>
 );
}

Line 4 is where we import the styles object. This object is then used to style components of our React app and is used just like any JavaScript object.

The thing to take home from this is that styles can be used and reused in multiple components. The styles just need to be imported and added to the style attribute.

Of course, there are situations where you should not use inline styling, and that’s where our next two methods come in.

Method #2: styled-components

With styled-components, we can write actual CSS in our JavaScript file. This means you can use all the features of CSS — like media queries, pseudo-selectors, nesting, etc. — in JavaScript.

styled-components uses ES6’s tagged template literals to style components. With it, the mapping between components and styles is removed. This means that when you’re defining your styles, you’re actually creating a normal React component that has your styles attached to it.

Using styled-components, we can create reusable components with styles. It is quite exciting to create and use. Explaining with an example here will do us a lot of good.

First, we need to install it, so run the following in your React app’s directory:

$ npm install --save styled-components

Let’s go back to our to-do app and make our component use styled components. First, we’ll import the styled-components package:

import styled from 'styled-components';

We can start using it right away. We’ll first create a styled component, then see how we’ll use it:

const TodoComponent = styled.div`
  background-color: #44014C;
  width: 300px;
  min-height: 200px;
  margin: 30px auto;
  box-sizing: border-box;
`;

Above we created a component that can be used the same as any React component. However, notice that we are using pure CSS in a JavaScript file. Next, let’s put this component to use:

function ToDoApp() {
 //...
 
 return (
   <TodoComponent>
     <h2>ToDo</h2>
     <div>
       <Input onChange={this.handleChange} />
       <p>{this.state.error}</p>
       <ToDoList value={this.state.display} />
     </div>
   </TodoComponent>
 );
}

In the code above, we used the styled component we created — TodoComponent — on line 5 like we’ll use any other HTML element. The only difference is that it comes with its own predefined styles.

We can do the same for other parts of the component:

function ToDoApp() {
 //...
 
 return (
   <TagComponent>
     <Header>ToDo</Header>
     <div>
       <Input onChange={this.handleChange} />
       <ErrorMessage>{this.state.error}</ErrorMessage>
       <ToDoList value={this.state.display} />
     </div>
   </TagComponent>
 );
}

To find out more about styled components and how to use them, you can read the official documentation here.

Now let’s discuss the third and final way to style in React.

Method #3: CSS Modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. Take note of the words scoped locally. Let’s break that down a little bit.

CSS class names and animation names are scoped globally by default. This can lead to conflict, especially in large stylesheets; one style can override another. This is the problem CSS Modules solves. CSS classes are only available within the component where they are used.

A CSS module is basically a .css file that is compiled. When compiled, it produces two outputs. One is CSS that is a modified version of input CSS with the renamed class names. The other is a JavaScript object that maps the original CSS name with the renamed name.

Let’s see an example of how this works. However, if you want to dig deeper, check out this article on it. Alright, let’s create a CSS class in a module for a sample error message. The name of our module is styles.css:

.error-message {
  color: red;
  font-size: 16px;
}

When compiled, this will produce something like this:

.error-message_jhys {
  color: red;
  font-size: 16px;
}

The added jhys is just a sample key (which I added myself) that is used to uniquely identify this class. As said earlier, it produces a JS object, which can be imported in the React file and used:

{
  error-message: error-message_jhys
}

Let’s see how we can use this now:

import styles from './styles.css';
 
function Message() {
 return (
   <div className={styles.ErrorMessage}>I am an error message</div>
 );
}

Remember that the main purpose of CSS Modules is to make CSS classes locally scoped and avoid conflicts in naming.

Method #4: Tailwind CSS

Tailwind CSS offers a different approach in which no CSS needs to be written to style an application. Instead, Tailwind CSS uses utility classes for each CSS property that you can use directly in your HTML or JSX.

Perhaps you are wondering, if each CSS attribute is mapped to a class, what is the bundle size for the final CSS? In fact, the bundle size is very small, with most projects shipping bundle sizes lower than 10kb. How? During building time, Tailwind CSS processes the classes you are using and builds a CSS bundle tailored to your project.

TailwindCSS offers much more than CSS classes mapped to attributes, it’s also a complete framework that supports responsive behaviour, grids, states, dark mode, and it’s highly configurable.

To set up TailwindCSS on a CRA project, there are a few steps (and libraries) involved because of what it requires modify the build process of the application to generate the CSS bundles.

First, begin by installing the libraries:

$ npm install -D [email protected]:@tailwindcss/postcss7-compat 
@tailwindcss/postcss7-compat [email protected]^7 [email protected]^9

Note that all Tailwind-related libraries are installed as dev packages, meaning that they won’t affect your JS bundle size.

Now let’s install and configure CRACO. Because CRA doesn’t let you override the PostCSS configuration natively, we also need to install CRACO to be able to configure Tailwind:

$ npm install @craco/craco

Once installed, update your package.json to use CRACO instead of react-scripts:

{
 // ...
  "scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "react-scripts eject"
 },
}

Let’s now create a CRACO configuration file and add TailwindCSS as a postcss plugin.

// craco.config.js
module.exports = {
 style: {
  postcss: {
   plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
   ],
  },
 },
}

Tailwind CSS offers many configuration options during its set-up, so if you want to learn more, check out the official documentation.

Now we need to set up our CSS baseline. Because Tailwind CSS uses multiple classes with relative values, it is important to set up the same CSS style base across all browsers. Tailwind CSS comes with some default styling to do just that.

Create a CSS file that you can include in your root component and add the following code:

/* ./your-css-folder/styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

💡 Note that importing these classes will make all your default elements behave differently than what you expect, so elements such as headings and paragraphs will have the same styling properties until you add Tailwind CSS classes to them.

Finally, use Tailwind CSS classes in your project. With your setup ready, you can now use CSS classes directly on your project. Go ahead and build a ToDo component using Tailwind CSS:

function ToDoApp() {
 //...

 return (
   <div className="bg-gray-100 rounded-xl p-8">
     <h2 className="text-lg font-semibold">ToDo</h2>
     <div>
       <Input onChange={this.handleChange} />
       <p className="font-medium color-red-100">{this.state.error}</p>
       <ToDoList value={this.state.display} />
     </div>
   </div>
  );
}

Notice these classes are directly injected as text to the className prop, and that there is a complete reference to all the class names with states and responsive attributes that can be used. Though it may seem counterintuitive at first, once you work with Tailwind CSS for a few days, chances are you will love it.

Conclusion

So, there we have it — four ways to style a React component. Generally, all the methods are useful, and depending on the project’s size, you can use whichever.

Hope you learned a thing or two about styling in React. If you have any questions or feedback, please leave a comment below.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .



Neo Ighodaro Neo Ighodaro is a twenty-something-year-old fullstack web developer and graphic designer not based in Lagos, Nigeria.

Leave a Reply