VOOZH about

URL: https://blog.logrocket.com/styling-react-6-ways-style-react-apps/

⇱ Styling in React: 6 ways to style React apps - LogRocket Blog


2024-05-31
3951
#css#react
Neo Ighodaro
385
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Editor’s note: This article was last updated by Emmanuel Odioko on 31 May 2024 to cover using React Hooks like useState and useEffect to implement dynamic styling to accomplish things like switching themes and responsive layouts.

👁 Styling In React: 6 Ways To Style React Apps

So, you’re here to learn about styling in React? Well, you’ve come to the right place. In this article, we will review a variety of methods for styling React components, including inline styling, using styled-components, CSS Modules, and more. By the end of this guide, you’ll be ready to pick the best styling option for your project.

Check out this video tutorial for more information:

Four ways to style in React

Learn about styling in React using CSS modules, inline styling, styled-components, and Tailwind CSS with Real Tough Candy. You can find the original blog post here on the LogRocket blog: https://blog.logrocket.com/the-best-styling-in-react-tutorial-youve-ever-seen-676f1284b945/?youtube-tutorial 00:00 Intro 00:59 What is inline styling in React? 03:20 Creating a style object variable 04:43 Should I use styled-components in React?

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Setting up your React application

We will use Create React App (CRA) to set up a demo React application. Here is a demo of the app:

Now, let’s jump into our first styling method.

Styling React components with inline styling

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 can add inline styles to any React component we want to render. These styles are written as attributes and are then passed to the element. Let’s style parts of our component using inline styles:

/** src/todo/AddTodo.js **/

//...code omitted for brevity
const AddTodo = () => {
 //...
 return (
 <div style={{ display: "flex", flexDirection: "column" }}>
 <h2 style={{ padding: "10px 20px", textAlign: "center", color: "white" }}>
 TODO
 </h2>
 <div
 style={{
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
 }}
 >
 <label
 style={{ padding: "10px 20px", textAlign: "center" }}
 htmlFor="new-todo"
 >
 What needs to be done?
 </label>
 </div>
 <div
 style={{
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
 }}
 >
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </div>
 {message && (
 <div
 style={{
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
 }}
 >
 <h4 style={{ color: "red" }}>{message}</h4>
 </div>
 )}
 </div>
 );
};
export default AddTodo;

In the code above, we added inline styles to the HTML elements in the AddTodo(). There are a few important things to note about this method.

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 create an object literal. The styles are then passed as object literals to the element.

N.B., JSX is a preprocessor step that adds XML syntax to JavaScript. You can 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 dash.

In the code above, we just added a few properties to the elements we styled. But what if we had many more styles to add to the element? This is where the inline method breaks down because it will no longer look clean.

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

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.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

So, instead of adding the styles inline directly, like we did in the previous example, we can pass the object variables as you see below:

/** src/todo/AddTodo.js **/
const AddTodo = () => {
//...
 return (
 <div style={Container}>
 <h2 style={Header}>TODO</h2>
 <div style={LabelContainer}>
 <label style={Label} htmlFor="new-todo">
 What needs to be done?
 </label>
 </div>
 <div style={FormContainer}>
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </div>
 {message && (
 <div style={Message}>
 <h4 style={{ color: "red" }}>{message}</h4>
 </div>
 )}
 </div>
 );
};

const Container = { display: "flex", flexDirection: "column" };
const Header = { padding: "10px 20px", textAlign: "center", color: "white" };
const LabelContainer = { display: "flex", justifyContent: "center", alignItems: "center"};
const Label = { padding: "10px 20px", textAlign: "center" };
const FormContainer = { display: "flex", justifyContent: "center", alignItems: "center"};
const ErrorMessage = { display: "flex", justifyContent: "center", alignItems: "center"};

In the code above, we created six object variables: Container, Header, LabelContainer, Label, FormContainer, and ErrorMessage. Then, we passed these variables to the element instead of typing them directly.

N.B., 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. Here is an example:

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, not the property values. However, it is possible to pass a variable as a value to a property. We can do this:

/* in .js file */
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 OK in React. Because files are not visible to other files unless imported, we can create as many object variables as we want — even with the same name — without conflict.

Sharing styles across multiple React components

The style objects and 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, we’ll add the following styles:

const Container = { display: "flex", flexDirection: "column" };
const Header = { padding: "10px 20px", textAlign: "center", color: "white" };
const LabelContainer = {
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
};
const Label = { padding: "10px 20px", textAlign: "center" };
const FormContainer = {
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
};
const ErrorMessage = {
 display: "flex",
 justifyContent: "center",
 alignItems: "center"
};

export const styles = {
 Container: Container,
 Header: Header,
 LabelContainer: LabelContainer,
 Label: Label,
 ErrorMessage: ErrorMessage,
 FormContainer: FormContainer
}

export const styles = {
 Container: Container,
 Header: Header,
 LabelContainer: LabelContainer,
 Label: Label,
 ErrorMessage: ErrorMessage,
 FormContainer: FormContainer
}

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

Therefore, creating an object that contains all styles makes more sense. This object will be exported and imported once to the component where it will be used. Let’s do this:

/** AddTodo.js file **/

// Import the styles
import {styles} from "./styles";


const AddTodo = () => {
//....
 return (
 <div style={styles.Container}>
 <h2 style={styles.Header}>TODO</h2>
 <div style={styles.LabelContainer}>
 <label style={styles.Label} htmlFor="new-todo">
 What needs to be done?
 </label>
 </div>
 <div style={styles.FormContainer}>
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </div>
 {message && (
 <div style={styles.ErrorMessage}>
 <h4 style={{ color: "red" }}>{message}</h4>
 </div>
 )}
 </div>
 );
};

Above AddTodo(), we’ll import the styles object. This object is then used to style the components of our React app and is used just like any JavaScript object.

Pros and cons of inline styling

The thing to understand from this method is that styles can be used and reused in multiple components, they just need to be imported and added to the style attribute. Using inline styling can help you quickly prototype your interface.

Of course, there are situations where you should not use inline styling. For example, the styles declaration can quickly get messy and disorganized with inline styling. Additionally, this method does not encourage the DRY (Don’t Repeat Yourself) principle. Using inline styling is not suitable for large projects with a lot of code.

This is where our next two methods come in.

Styling with styled-components

With styled-components, we can write actual CSS in our JavaScript files. This allows us to use all the features of CSS — like media queries, pseudo-selectors, nesting, and more — directly within our JavaScript.

styled-components use ES6’s tagged template literals to style components. This approach removes the mapping between components and styles, meaning that when you define your styles, you’re actually creating a normal React component with your styles attached to it.

With styled-components, we can create reusable components with styles. To get started, install styled-components by running $ npm install --save styled-components in your React app’s directory.

Next, we’ll incorporate styled-components into our to-do app. Import the styled-components package in your AddTodo.js file with import styled from 'styled-components';.

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

/** AddTodo.js file **/
const Container = styled.div`
 display: flex;
 flex-direction: column;
`;
const Header = styled.div`
 padding: 10px 20px;
 text-align: center;
 color: white;
`;
//....

In the code 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:

/** AddTodo.js file **/

function AddTodo() {
 //...


return (
 <Container>
 <Header>TODO</Header>
 <LabelContainer>
 <Label htmlFor="new-todo">What needs to be done?</Label>
 </LabelContainer>
 <FormContainer>
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </FormContainer>
 {message && (
 <ErrorContainer>
 <ErrorMessage>{message}</ErrorMessage>
 </ErrorContainer>
 )}
 </Container>
 );
}

In the code above, we used the styled-component we created and replaced the HTML tags with the defined component style tags. The styled-component is used like any other HTML element. The only difference is that it comes with its own predefined styles. You can access the code in the CodeSandbox playground.

Pros and cons of styled-components

styled-components dynamically style your elements in whichever way you deem fit. They encourage the DRY principle with great patterns for organizing your code, and they have compatibility with a wide range of frameworks and libraries. They are also great for developing and maintaining design systems.

However, styled-components come with a large computing overhead when converting declarations to plain CSS. This can affect the performance of your application. styled-components also require time to get familiar with the syntax and procedures.

If you are looking for an alternative for styled-components, check out Emotion. Next, let’s discuss a third way to style in React.

Styling React components with CSS Modules

A CSS Module is a CSS file in which all class and animation names are scoped locally by default. Take note of the words “scoped locally.”

CSS class names and animation names are scoped globally by default. This can lead to conflict, especially in large style sheets, because one style can override another. CSS Modules solve this problem by scoping CSS classes locally, ensuring they are only available within the component where they are used.

A CSS Module is essentially a .css file that is compiled. When compiled, it produces two outputs. One is CSS, 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.

Now, let’s create a CSS class in a module for a sample error message. The name of our module is styles.module.css:

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

When compiled, it will produce something like this:

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

The added jhys is a sample key (that I added myself) that is used to uniquely identify this class. As mentioned earlier, it produces a JavaScript object, which can be imported into the React file and used like so:

{
 error-message: error-message_jhys
}

Now, let’s see how we can use it:

/** .js file **/
import styles from './styles.css';


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

Pros and cons of CSS Modules

CSS Modules can easily integrate with CSS or SCSS styling engines. They generate unique class names that are void of conflicts and have built-in support in React. CSS Modules also fix global scope issues for CSS declarations.

However, referencing class names with CSS Modules can oftentimes be confusing.

React styling with Tailwind CSS

Tailwind CSS offers a different approach in which no CSS needs to be written to style a React 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 that if each CSS attribute is mapped to a class, what is the bundle size for the final CSS? The bundle size is actually very small, with most projects shipping bundle sizes lower than 10kB. But how? Tailwind CSS processes the classes you use during build time and builds a CSS bundle tailored to your project.

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

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

First, begin by installing Tailwind CSS and generating tailwind.config.js and postcss.config.js. Then, run the $ npm install -D tailwindcss postcss autoprefixer and $ npx tailwindcss init -p commands.

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

In your tailwind.config.js file generated above, add the following value to the content array:

/** @type {import('tailwindcss').Config} */
module.exports = {
 content: ["./src/**/*.{js,jsx,ts,tsx}",],
 theme: {
 extend: {},
 },
 plugins: [],
}

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.

To achieve this, navigate to your src/index.css file and paste the following code:

/* ./src/styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

N.B., 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.

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

With your setup ready, you can now use CSS classes directly on your project. Build a ToDo component using Tailwind CSS:

/** AddTodo.js file **/

function AddTodo() {
 //...

 return (
 <div className="flex flex-col">
 <h2 className="px-2.5 py-5 text-center text-white">TODO</h2>
 <div className="flex justify-center items-center">
 <label className="px-2.5 py-5 text-center" htmlFor="new-todo">
 What needs to be done?
 </label>
 </div>
 <div className="flex justify-center items-center">
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </div>
 {message && (
 <div className="flex justify-center items-center">
 <h4 className="text-red-800">{message}</h4>
 </div>
 )}
 </div>
 );
}

Notice that 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.

Pros and cons of styling with Tailwind CSS

Tailwind CSS is easy to use for customizing elements and building clean user interfaces. Tailwind CSS offers a huge reduction in writing custom CSS code, which allows you to develop UI screens faster.

However, Tailwind CSS often requires you to implement it from scratch, as basic components like buttons, navbars, and tabs are not provided. Additionally, learning Tailwind CSS will require a minimal learning curve.

Using Sass in React

Sass (Syntactically Awesome Style Sheets) is a CSS preprocessor with many features for creating reusable styles, functionalities for nesting, and organizing CSS declarations. Using Sass or plain CSS is based on writing styles in an external file and importing it into the component that needs it.

In summary, Sass is all about writing standard CSS with added benefits. You can also write your styles using Sass or SCSS syntax.

This guide uses the SCSS syntax to demonstrate its usage in the to-do application. To get started, add the node-sass dev dependency to your project with the npm install node-sass command. Also, update your .css file to a .scss file extension.

In the code below, you will see a mixture of CSS and SCSS syntax. The SCSS engine permits writing plain CSS while also leveraging its additional features:

/** todo.scss file **/

// declare global variables
$paddingVertical: 10px;
$paddingHorizontal: 20px;
$text-center: center;
$text-white: #ffffff;
$text-black: #000000;
$text-red: rgb(185 28 28);
$font-size: 16px;
.error {
 color: $text-red;
}
.container{
 display:flex;
 flex-direction: column;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
%typo-large {
 padding: $paddingVertical $paddingHorizontal;
 text-align: $text-center;
}
.h2 {
 @extend %typo-large;
 color: $text-white;
}

.label {
 @extend %typo-large;
}

Next, import the styles declaration into your component. So, in the AddTodo file, import the contents of the todo.scss files. Then you can access your styles as depicted below:



/** AddTodo.js file**/

//...imports omitted for brevity

// Import the styles
import "./todo.scss";


function AddTodo() {
 //...code omitted for brevity
return (
 <div className="container">
 <h2 className="h2">TODO</h2>
 <div className="flex-center">
 <label className="label" htmlFor="new-todo">
 What needs to be done?
 </label>
 </div>
 <div className="flex-center">
 <form onSubmit={handleSubmit}>
 <input onChange={onChange} value={task} ref={inputRef} />
 <button>Add </button>
 </form>
 </div>
 {message && (
 <div className="flex-center">
 <h4 className="error">{message}</h4>
 </div>
 )}
 </div>
 );
}

Here is the CodeSandbox playground for the above example.

Pros and cons of Sass and CSS

Sass offers a wide range of features for dynamically styling user interfaces and has a large community of users and support. It also integrates easily with standard CSS code.

Unfortunately, learning Sass can be challenging due to its steep learning curve, and it requires effort to properly structure and organize your SCSS code.

Using React Hooks for dynamic styling

Dynamic styling is seen in almost every application, but in case it’s new to you, it is the ability to change the appearance of an application based on a user’s interaction. Popular use cases include theme switching, responsive layouts, and interactive animation. In React, this can be accomplished using React Hooks.

React provides us with Hooks like useState and UseEffect, which are primarily used for state management and side effects in functional components. However, they can also be used for dynamic styling.

Theme switching

Because most developers dislike the light mode and find it annoying to code with it, let’s take a look at this example of theme switching in VS Code:

import React, { useState } from 'react';
import './App.css';
const App = () => {
 const [theme, setTheme] = useState('light');
 const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');
 return (
 <div className={`app ${theme}`} style={{height: '100vh', width: '100vw'}}>
 <button onClick={toggleTheme}>Toggle Theme</button>
 <p>LogRocket</p>
 </div>
 );
};
export default App;


CSS
.light {
 background-color: white;
 color: black;
}
.dark {
 background-color: black;
 color: white;
}

When you click the Toggle Theme below, you get to toggle between two backgrounds:

👁 Switching To Dark Theme Using React Hooks

👁 Switching To Light Theme Using React Hooks

In this example, we demonstrated how to use the useState and useEffect React Hooks to implement an interactive and responsive interface that switches between a light and dark mode.

Responsive layout

Using the useState and useEffect Hooks also helps ensure your component adapts to different screen sizes, whether on mobile or desktop.

useState keeps track of the current window width, and when the window is resized, useEffect is used to update the width. This is similar to using an event listener in vanilla JavaScript for window resizing.

There’s a more appropriate way to do this using the ResizeObserver API, but this method of responsiveness is still relevant. Let’s see how it works:

import React, { useState, useEffect } from 'react';
const ResponsiveComponent = () => {
 const [windowWidth, setWindowWidth] = useState(window.innerWidth);

 useEffect(() => {
 const handleResize = () => setWindowWidth(window.innerWidth);
 window.addEventListener('resize', handleResize);
 return () => window.removeEventListener('resize', handleResize);
 }, []);
 const isMobile = windowWidth < 768;
 return (
 <div style={{
 padding: isMobile ? '10px' : '20px',
 backgroundColor: isMobile ? 'lightblue' : 'lightgreen',
 }}>
 <p>{isMobile ? 'Mobile View' : 'Desktop View'}</p>
 </div>
 );
};
export default ResponsiveComponent;

Pros and cons of styling with React Hooks

Dynamic styling makes an application more engaging and responsive. But keep in mind that dynamic styles introduce a high possibility of future bugs. This is because there are several factors involved when an application has multiple components, such as managing state changes, conditional rendering, and ensuring consistency across those components.

General optimization tips for styling

In this guide, we have explored the pros and cons of various styling approaches, noting that the cons can lead to poor performance, especially in large projects. Keep these tips in mind when styling:

  • Every time a component re-renders, the styled-components re-build the style sheet. This isn’t ideal for large projects because it can slow them down, but you can avoid unnecessary re-renders to mitigate this issue
  • Avoid welcoming unused CSS into your project. Many CSS frameworks come with styles that aren’t used, which can negatively impact performance. Using PurgeCSS can help remove these unnecessary styles, if the framework doesn’t already do this automatically
  • It is also advisable to use code splitting in CSS; this practice helps your application only load CSS for the parts that need it

Most of the performance overhead can be traced to bulky projects, so if you have a light project, you will probably avoid these issues.

Conclusion

While many libraries and CSS engines provide ways for styling in your React application, it is important to note that there is not a one-size-fits-all approach to choosing the best React styling solution.

When looking at the best way to style a React application, several factors vary in different software teams. Here are a few considerations:

  • What are the performance metrics?
  • How easy is it to optimize your code?
  • Do you require a design system?
  • Does the CSS system require extra effort to customize your UI?

These are some of the questions that you will need to answer before deciding the best way to style in React. Ultimately, the team plays a major role in selecting a CSS solution that best fits a product requirement while considering the expertise and domain knowledge of the team.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side

    $ npm i --save logrocket 
    
    // Code:
    
    import LogRocket from 'logrocket'; 
    LogRocket.init('app/id');
     
    // Add to your HTML:
    
    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

👁 Image
Emmanuel John
Jun 17, 2026 ⋅ 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

👁 Image
Chizaram Ken
Jun 16, 2026 ⋅ 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read
View all posts

Hey there, want to help make our blog better?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now