Codementor Events

Style your React Components like a Boss with styled-components

Published Jul 07, 2022
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.

What is styled-components?

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.
Screenshot 2022-07-07 at 14.03.10.png
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:
Screenshot 2022-07-07 at 14.08.40.png
It gives you everything you'll need in those two backticks: colouring and intellisense (VSCode Recommended!)
Screenshot 2022-07-07 at 14.10.03.png
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:
Screenshot 2022-07-07 at 14.17.37.png
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:
Screenshot 2022-07-07 at 15.06.06.png
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:
Screenshot 2022-07-07 at 15.17.44.png
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:
Screenshot 2022-07-07 at 15.58.25.png
Screenshot 2022-07-07 at 15.58.50.png
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

Discover and read more posts from Sean Hurwitz
get started
post commentsBe the first to share your opinion
SolomonHuerta
17 days ago

Thanks for sharing it with us. I need an essay writing service for an essay. If you need some tips for your personal leadership essay, Check out DoMyPapers’ sample essays at https://domypaper.com/samples/personal-leadership They’re a great resource for getting ideas and inspiration for your own writing!

Thomas Theiner
2 years ago

Thank you Sean,

this again is a marvellous post I very much enjoyed reading. Keep up your great work for this community!

Sean Hurwitz
2 years ago

Thanks so much for the feedback Thomas! It’s greatly appreciated <3

Show more replies