Style your React Components like a Boss with styled-components
Hello Codementor! Today I will be sharing my love of the styled-components
library and why it's always my first choice when it comes to styling my React Components. We'll look at some nifty features and then build our own Light/Dark mode switch! Come code along with me.
styled-components
?
What is As it says on their website, styled-components
uses all that's lovely from ES6 and CSS to help you style components. Another great feature is that it accepts props like any normal React component, so you never have to deal with stylesheets and your styling feels like you're just writing React, which is awesome because React is awesome and you can be singularly focused in your development. Not to say you don't need to know good CSS, but the way of doing things is just so much more intuitive, and that's why I always vouch for this library.
Setup
Let's spin up a React App
npx create-react-app --template typescript styled
cd styled
code .
If the last line doesn't work, open up the folder in your favourite Text Editor
I add a css reset to index.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
And well done! That's the last css file you're going to touch!
Let's install the dependencies we need:
npm i styled-components @types/styled-components polished
The polished
library is a wonderful complement to styled-components
. You can look at their docs as well
Let's make a Button
I will create a components
folder and inside that, an index.ts
file and a folder named Button
, with two files: index.tsx
and styles.ts
.
In the index.ts
directly within components
, add:
export { default as Button } from "./Button";
Now, let's turn our attention to the styles.ts
file we made. Start with this:
import styled from "styled-components";
const ButtonContainer = styled.div`
align-items: center;
background: orange;
border-radius: 10px;
color: white;
cursor: pointer;
display: flex;
font-weight: bold;
height: 40px;
justify-content: center;
max-width: 200px;
padding: 1rem;
`;
export { ButtonContainer };
This is how you construct a simple Styled Component! Notice it's an ES6 string Template Literal tagged function, where you are essentially writing plain text in between the two backticks. If the colour is plain, you most definitely need this:
It gives you everything you'll need in those two backticks: colouring and intellisense (VSCode Recommended!)
Now that you're set up with this, it'll feel like you're just writing regular css, but with the look and feel of a React Component!
Now let's implement it in our index.tsx
file:
import { ButtonContainer } from "./styles";
interface ButtonProps extends React.DOMAttributes<HTMLDivElement> {
secondary?: boolean;
}
const Button: React.FC<ButtonProps> = ({ children, ...rest }) => {
return <ButtonContainer {...rest}>{children}</ButtonContainer>;
};
export default Button;
I am extending the props to include a secondary
prop, and you'll see what we're going to do with that in a little bit.
Now, observe how we're not using className anywhere. That's the goal, essentially. The styles are all built into the components, and it looks a lot more React-esque and cleaner in my opinion.
Let's use it now! Change your App.tsx
to this:
import { Button } from "./components";
function App() {
return <Button>Hello!</Button>;
}
export default App;
And hopefully you should have this if you run npm run start
:
Now, what good is a button if it doesn't shine when you hover? so let's add some more styling to the ButtonContainer
:
transition: all 200ms ease;
:hover {
background: ${() => lighten(0.1, `#FAA500`)};
font-size: 1.2rem;
}
The hover
pseudo-selector comes in handy, and is much neater than the css equivalent of saying class:hover
each time. Here, the concerns are within the parent, which is nice.
All pseudo-selectors are available, such as ::before
, :nth-child
etc.
The next thing to note is how I can inject a function and return a value which will then become the value of the background. This will be important later.
If you hover over your button now, it should transition to a lighter colour with bigger text!
To target all elements within the current component, the syntax is such:
& p{
color: red
}
This, for example, will turn all the p
elements inside ButtonContainer
to red. Even nested ones. To select ONLY the next level down, it's:
> p{
color: red
}
So it's very customisable
Let's add a Theme Context!
styled-components
exposes a React Context that works wonderfully with every component you build. You can create a theme object and use it throughout your app. Let's see how.
First, for Typescript purposes, we are going to add a file styled.d.ts
in the root:
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
colors: {
primary: string;
background: string;
text: string;
};
headerHeight: string;
}
}
Now in your root, let's add ThemeProvider.tsx
:
import React, { useContext, useState } from "react";
import {
DefaultTheme,
ThemeProvider as SCThemeProvider,
} from "styled-components";
const theme: DefaultTheme = {
colors: {
primary: "red",
background: "#FFFFFF",
text: "#1a161b",
},
headerHeight: "50px",
};
const darkTheme: DefaultTheme = {
colors: {
primary: "red",
text: "#FFFFFF",
background: "#1a161b",
},
headerHeight: "50px",
};
interface LMProps {
isLightMode: boolean;
toggleLightMode: () => void;
}
const LightModeContext = React.createContext<LMProps>({
isLightMode: true,
toggleLightMode: () => {},
});
const LightModeProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [isLightMode, setLightMode] = useState(true);
const toggleLightMode = () => setLightMode((prev) => !prev);
return (
<LightModeContext.Provider value={{ isLightMode, toggleLightMode }}>
{children}
</LightModeContext.Provider>
);
};
const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const { isLightMode } = useToggleLightMode();
return (
<SCThemeProvider theme={isLightMode ? theme : darkTheme}>
{children}
</SCThemeProvider>
);
};
const useToggleLightMode = () => useContext(LightModeContext);
export { ThemeProvider, useToggleLightMode, LightModeProvider };
And update your index.tsx
:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import { LightModeProvider, ThemeProvider } from "./ThemeProvider";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<LightModeProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</LightModeProvider>
</React.StrictMode>
);
The output should be the same, but now we have created a global theme context. We can now use it in any styled component. Let's go back to the ButtonContainer
:
import { lighten } from "polished";
import styled from "styled-components";
const ButtonContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.colors.primary};
border-radius: 10px;
color: white;
cursor: pointer;
display: flex;
font-weight: bold;
height: 40px;
justify-content: center;
max-width: 200px;
padding: 1rem;
transition: all 200ms ease;
:hover {
background: ${({ theme }) => lighten(0.1, theme.colors.primary)};
font-size: 1.2rem;
}
`;
export { ButtonContainer };
Inside those function injections, I have access to the theme
object and can style accordingly. Try changing the primary color to red
in ThemeProvider.tsx
:
Voila! There's so much potential!
Making the Button Secondary
Now, on the Button
component, remember I made a prop secondary
? Let's use that! Once again, in ButtonContainer
:
import { lighten } from "polished";
import styled, { css } from "styled-components";
const ButtonContainer = styled.div<{ secondary?: boolean }>`
align-items: center;
background: ${({ theme }) => theme.colors.primary};
border-radius: 10px;
color: white;
cursor: pointer;
display: flex;
font-weight: bold;
height: 40px;
justify-content: center;
max-width: 200px;
padding: 1rem;
transition: all 200ms ease;
:hover {
background: ${({ theme }) => lighten(0.1, theme.colors.primary)};
font-size: 1.2rem;
}
${({ theme, secondary }) =>
secondary &&
css`
color: ${theme.colors.primary};
background: ${theme.colors.background};
border: 2px solid ${theme.colors.primary};
:hover {
background: ${theme.colors.primary};
color: white;
}
`}
`;
export { ButtonContainer };
styled-components
also has a css
tag, where you can write css within css! I can invoke a function on the base level which includes the prop I pass through and style accordingly. If I then add the secondary
prop to my button on App.tsx
, I get a secondary button:
Yay!
Build out a Light/Dark Mode Switch
Now I'll add a styles.ts
on my root and add a bunch of stuff:
import { lighten } from "polished";
import styled, { css } from "styled-components";
const Header = styled.div`
width: 100vw;
height: ${({ theme }) => theme.headerHeight};
background: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
font-weight: bold;
transition: all 200ms ease;
`;
const Body = styled.div`
width: 100vw;
height: calc(100vh - ${({ theme }) => theme.headerHeight});
margin-top: ${({ theme }) => theme.headerHeight};
padding: 2rem;
background: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
transition: all 200ms ease;
`;
const ToggleContainer = styled.div<{ isOn: boolean }>`
position: relative;
width: 40px;
height: 20px;
background: lightgray;
border-radius: 20px;
border: 1px solid grey;
cursor: pointer;
transition: all 200ms ease;
::after {
position: absolute;
transition: all 200ms ease;
content: "";
top: 2px;
left: 2px;
background: grey;
width: 15px;
height: 15px;
border-radius: 100px;
}
${({ theme, isOn }) =>
isOn &&
css`
background: ${lighten(0.4, theme.colors.primary)};
border-color: ${theme.colors.primary};
::after {
left: 22px;
background: ${theme.colors.primary};
}
`}
`;
export { Header, Body, ToggleContainer };
And add it to my App.tsx
as follows:
import { Fragment } from "react";
import { Body, Header, ToggleContainer } from "./styles";
import { useToggleLightMode } from "./ThemeProvider";
function App() {
const { isLightMode, toggleLightMode } = useToggleLightMode();
return (
<Fragment>
<Header>
<h3>A Header!</h3>
<ToggleContainer isOn={isLightMode} onClick={toggleLightMode} />
</Header>
<Body>A Body!</Body>
</Fragment>
);
}
export default App;
Notice, no classNames, and everything looks React-y!
The toggleLightMode
function from my useToggleLightMode
custom hook will switch between Light and Dark Mode When it does that, the theme
object passed to the ThemeProvider
context will change, and the entire app will mutate:
Oh, the power!
Wrapping up
We worked together to implement styled-components
as well as a Theme context. Using all that, we built a toggle for light / dark mode. We can go on about the library but it's getting late. I invite you to explore the documentation yourself and see how easy it is to do other stuff, like media queries and the like.
Til next time, Happy Coding!
~ Sean
I want to style my React components like a boss with styled-components and for that I am searching for information online and I am glad I found your post where you have explained it very well. It is very easy for me to understand your post. I am so happy because I also found the https://uktopwriters.com/review-ukessays/ website link through which I can easily find the best essay writing service. Now, I can easily choose the best essay writer by reading reviews on the given website.
Thanks for sharing it with us.
Thank you Sean,
this again is a marvellous post I very much enjoyed reading. Keep up your great work for this community!
Thanks so much for the feedback Thomas! It’s greatly appreciated <3