Styling in React page
Learn about different ways to apply CSS styles in React applications.
Overview
In this section, we will:
- Review how plain CSS works in React applications
- Introduce CSS Modules
- Create a component-specific stylesheet
- Apply styles in a component
Objective: Add a styled link
We have the navigation links in the header, but it’d be nice if our Home
component had a nice big Call to Action (CTA) in it so our users were directed to the next step: Choose a Restaurant.
Styling options in React applications
There is no prescribed approach for styling React apps. It’s entirely possible to style components using regular CSS. This approach works fine for many applications, especially smaller ones.
However, as your application grows in complexity, you might encounter challenges with style management. For instance, CSS written for one component can unintentionally affect other parts of your application.
The community has built several styling options. We will be using CSS Modules as it is one of the most popular styling libraries and is very approachable for anyone that is used to styling in plain CSS. CSS Modules offer a way to write CSS that’s scoped to individual components, thus preventing style clashes and maintaining a cleaner codebase.
Reviewing how plain CSS works
In a standard React application using regular CSS, styles are global. This means that any style you define can potentially affect any element in your application that matches the style’s selector.
Let’s imagine that we have two components in our application: <BlogPost>
and <UserProfile>
. Both of them feature an avatar image, one for the author of a post and the other for the signed-in user.
Here’s what the BlogPost.css
file might look like:
.avatar {
float: left;
margin-right: 10px;
}
And the BlogPost.tsx
component:
import "./BlogPost.css"
const BlogPost: React.FC = ({ authorAvatar, content }) => {
return (
<article>
<img alt="Headshot showing…" className="avatar" src={authorAvatar} />
<p>{content}</p>
</article>
)
}
Here’s what the UserProfile.css
file might look like:
.avatar {
border-radius: 50%;
height: 100px;
width: 100px;
}
And the UserProfile.tsx
component:
import "./UserProfile.css"
const UserProfile: React.FC = () => {
return (
<img alt="User avatar showing…" className="avatar" src="user-profile.jpg" />
)
}
What will the final CSS be?
In this case, the two style declarations combined will be applied to both images:
.avatar {
border-radius: 50%;
float: left;
height: 100px;
margin-right: 10px;
width: 100px;
}
This is no good! When someone edits one component, it looks like they’re only changing the styles for that single component, but in fact they are changing any instance where that class
name is used globally throughout the entire application.
Let’s solve this!
Introducing CSS modules
CSS Modules works like regular CSS, but the class names are randomized to prevent conflicts between components. When using CSS Modules, if multiple components implement a .avatar
class, those classes will be unique and conflict-free because CSS Modules will rename each class with a unique random string like .avatar_R8f2
.
For example, these styles in a CSS module:
/* BlogPost.module.css */
.avatar {
float: left;
margin-right: 10px;
}
Will be converted into the following:
/* bundle.css */
.avatar_R8f2 {
float: left;
margin-right: 10px;
}
The standard procedure with CSS Modules is to create a stylesheet for each component. That stylesheet is placed in the same folder as the component. In order to identify the file as a CSS Module, the filename should end with .module.css
. The file structure for a BlogPost component will look like the following:
- BlogPost
- BlogPost.module.css
- BlogPost.test.tsx
- BlogPost.tsx
- index.ts
Importing and applying styles
During the build process, the CSS classes will be randomized and added to a global stylesheet. The randomized class names are imported into components as an object where the key is the original class and the value is the new, randomized string.
Here’s an example of what the JS object imported from BlogPost.module.css
might look like:
export default { avatar: "avatar_R8f2" }
In order to add the .avatar
class to a component, import the CSS Modules styling object and apply the class using the original class name as the key. Note the HTML class
attribute is renamed to className
in JSX because class
is a reserved word in JavaScript.
import styles from "./BlogPost.module.css"
const BlogPost: React.FC = ({ authorAvatar, content }) => {
return (
<article>
<img
alt="Headshot showing…"
className={styles["avatar"]}
src={authorAvatar}
/>
<p>{content}</p>
</article>
)
}
Avoid plain selectors
Note that CSS Modules cannot rename HTML tags, so all of your styling should use classes to avoid unexpected styling bugs. The following will apply to all img
elements in the project and should be avoided.
/* Don’t do this with CSS Modules as tag names can not be randomized */
img {
float: right;
}
Instead, make sure tags are used together with classes.
img.fancyImage {
float: right;
}
.avatar img {
float: right;
}
Install CSS Modules
Already done! Vite, our build tool, ships with first-class CSS Modules support. Vite also supports a few other styling options out of the box.
Setup
We’ve created some CSS for this exercise. Create a CSS Modules file and copy the following styles into it.
✏️ Create src/pages/Home/Home.module.css and update it to be:
a {
background-color: #337ab7;
border-color: #2e6da4;
border-radius: 6px;
color: #fff;
display: inline-block;
font-weight: normal;
padding: 10px 16px;
}
a:focus {
background-color: #286090;
border-color: #122b40;
color: #fff;
text-decoration: none;
}
a:hover {
background-color: #286090;
border-color: #204d74;
color: #fff;
text-decoration: none;
}
a:active {
background-color: #204d74;
border-color: #122b40;
color: #fff;
text-decoration: none;
}
Verify
✏️ Update src/pages/Home/Home.test.tsx to be:
import "@testing-library/jest-dom"
import { render, screen } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import { beforeEach, describe, expect, it } from "vitest"
import Home from "./Home"
describe("Home component", () => {
beforeEach(() => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>,
)
})
it("renders the image with correct attributes", () => {
const image = screen.getByAltText(/Restaurant table with glasses./i)
expect(image).toBeInTheDocument()
expect(image).toHaveAttribute("width", "250")
expect(image).toHaveAttribute("height", "380")
})
it("renders the title", () => {
const titleElement = screen.getByText(
/Ordering food has never been easier/i,
)
expect(titleElement).toBeInTheDocument()
})
it("renders the description paragraph", () => {
const description = screen.getByText(/We make it easier/i)
expect(description).toBeInTheDocument()
})
it("renders the link to the restaurants page", () => {
const link = screen.getByRole("link", { name: /choose a restaurant/i })
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute("href", "/restaurants")
})
})
Exercise
Now that we’ve learned to apply styling in React with CSS Modules, it’s time to practice by styling a link in the Home component. You’ll bring in a Link
component from React Router.
- Update the styles in
Home.module.css
to be usable as a CSS Module. - Update the
<Home>
component to include a styled link:- Use
<Link>
(from the previous section!) to create a link to the/restaurants
page. - Import the
styles
fromHome.module.css
and apply them to the new link.
- Use
Having issues with your local setup? You can use either StackBlitz or CodeSandbox to do this exercise in an online code editor.
Solution
Click to see the solution
✏️ Update src/pages/Home/Home.module.css to be:
.chooseButton {
background-color: #337ab7;
border-color: #2e6da4;
border-radius: 6px;
color: #fff;
display: inline-block;
font-weight: normal;
padding: 10px 16px;
}
.chooseButton:focus {
background-color: #286090;
border-color: #122b40;
color: #fff;
text-decoration: none;
}
.chooseButton:hover {
background-color: #286090;
border-color: #204d74;
color: #fff;
text-decoration: none;
}
.chooseButton:active {
background-color: #204d74;
border-color: #122b40;
color: #fff;
text-decoration: none;
}
✏️ Update src/pages/Home/Home.tsx to be:
import HeroImage from "place-my-order-assets/images/homepage-hero.jpg"
import { Link } from "react-router-dom"
import styles from "./Home.module.css"
const Home: React.FC = () => {
return (
<div className="homepage" style={{ margin: "auto" }}>
<img
alt="Restaurant table with glasses."
height="380"
src={HeroImage}
width="250"
/>
<h1>Ordering food has never been easier</h1>
<p>
We make it easier than ever to order gourmet food from your favorite
local restaurants.
</p>
<p>
<Link className={styles.chooseButton} to="/restaurants">
Choose a Restaurant
</Link>
</p>
</div>
)
}
export default Home
Having issues with your local setup? See the solution in StackBlitz or CodeSandbox.
Next steps
In the next section, we’ll learn to manage state.