DEV Community

Cover image for Complete Guide On How To Use Styled-components In React
Elijah Trillionz
Elijah Trillionz

Posted on • Updated on

Complete Guide On How To Use Styled-components In React

I have often found styling a React application very confusing and difficult. Initially, I was confused about how to structure my CSS files, thoughts like "should I dedicate a specific CSS file to specific pages? or should I just use one file for the whole app (which is scary, but I've done it :))?" are always roaming through my head when I am creating a new app in React.

Dedicating a CSS file to a specific page or component is ideal, but there are backsides to this. One of which is that a child page/component that has its own style will also inherit the styles of the parent page/component. This will cause a conflict, you will end up using important half the time in your child page/component.

Some can organize this approach properly, but it still is very hard. Then there is Next.js (CSS module) which has done a marvelous job in simplifying this approach for its users. In Next.js, each page/component can have a dedicated style (CSS file). Conflicts do not exist because the styles from any CSS file will only be used if called upon as a class name. But still, this is not my best approach for scaling apps, because of semantic class names.

Then there is Tailwind, this is where some developers have come to rest, but for me it made my JSX look too scary. I made a simple form with it, and I felt like I have made a full website, with lots of overwhelming abbreviated class names that I don't understand. Mind you, I didn't copy and paste. Am not saying Tailwind is bad, I just don't like the way it makes my JSX bucky and rough.

Then I came across styled-components that changed everything. This time I could style everything and anything I want without worrying about conflicts and without using class names in my JSX. That's amazing. Styled-components is basically what the name says: "styled-components". Like "this is a styled component (e.g header)". It's a component that is styled not by using a CSS file, but by using CSS syntax in JavaScript (components to be precise).

Let's now take a quick look at what styled-components is, and how it works which will get us acquainted with the styled-components syntax.

What is styled-components

Styled-components allows you to create components and attach styles to it using ES6 tagged template literals. The styles attached are written in CSS. The code below shows an example of a styled Button component

import styled from 'styled-components';

const Button = styled.button`
  padding: 10px;
  border: 2px solid blue;
  border-radius: 4px;
`;

const Example1 = () => {
  return (
    <main>
      <Button>Click me</Button>
    </main>
  );
};

export default Example1;
Enter fullscreen mode Exit fullscreen mode

From the code above, we can see CSS being used in JavaScript template literals to attach styles to the Button component. The styled object which is imported from styled-components, contains tons of HTML elements as methods that represent what the component is.

For example, the button method above is a function that represents the HTML element "button". This means that the Button component is a button that can be used anywhere in our app just like any other component. Just as we've used it in the Example1 component thereby giving us a styled clickable reusable button component.

The Syntax

const Button = styled.button`
  padding: 10px;
  border: 2px solid red;
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

There is nothing new here, apart from the template literals being attached to a function. If you're not familiar with tagged template literals, this will be new to you and may look confusing too, it was introduced in ES6.

Recall that we mentioned earlier that the keyword button there is a method (object function), and as such what we ought to do is call it and pass some arguments in it right? to be something like

const Button = anObject.method('Some arguments');
Enter fullscreen mode Exit fullscreen mode

Well, that is what we just did, we just called the function and passed in an argument. You say how? Tagged template literals allow you to pass string interpolations as an argument in a function. The result of that argument is an array of the strings passed into the function.

func`ok World` // is equivalent to
func([ok World])
Enter fullscreen mode Exit fullscreen mode

This introduction to styled-components will help you understand this better.

Also worth noting from the syntax above is the purpose of the button method. We've said before that the button method is what tells React to treat the component like a button, and not any other element. Traditionally, it is the same as this

const Button = ({ children }) => {
  return <button>{children}</button>;
};
Enter fullscreen mode Exit fullscreen mode

If we wanted a link rather than a button, then we would say

const LinkButton = styled.a`
  padding: 10px;
  border: 2px solid red;
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

So basically this is the way styled-components works. The code below shows an illustration of the syntax

const ComponentName = styled.element`
  css_property: css_value;
`;
Enter fullscreen mode Exit fullscreen mode

Where:

  1. ComponentName can be any name
  2. element can be any supported JSX element
  3. css_property represents a property name in CSS
  4. css_value represents the value for the property name from 3

Styling Component's Children

So far we've only applied styled-components to a single element containing component. But what if the component is to have child elements that have to be styled as well, does it mean we will have to create a styled component for each element? No, we don't, we can apply styles to child elements like this

import styled from 'styled-components';

const Header = styled.header`
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 15px;
  background: #000;

  h2 {
    font-weight: 400;
    color: violet;
    font-size: 1rem;
  }

  li {
    list-style: none;
    display: inline-block;
    color: #ccc;
  }
`;

const Example2 = () => {
  return (
    <div>
      <Header>
        <h2>Hello World</h2>
        <ul>
          <li>About</li>
        </ul>
      </Header>
      <main>
        <h2>Hello World Paragraph!</h2>
      </main>
    </div>
  );
};

export default Example2;
Enter fullscreen mode Exit fullscreen mode

Preview the example above and you'd notice only the h2 element in header takes the violet color. This is because we've only applied styles to the Header component and its children, not to every matching element. This is possible because styled-components creates a unique class name for each component (styled component) we create. As such the styles of the child elements of the component will be identified with the class name. Inspect the example above in your browser dev tools see the class names generated for the Header component.

Applying Pseudo-classes and Pseudo-elements

It is possible to apply pseudo-classes (e.g :hover) or/and pseudo-elements (e.g ::after) to a styled component. Say we have a button to change the border color when hovered on, we would have

const Button = styled.button`
  padding: 10px;
  border: 2px solid red;
  border-radius: 4px;

  &:hover {
    border-color: blue;
  }
`;
Enter fullscreen mode Exit fullscreen mode

Here we have used the ampersand (&) character to reference the current element of the component. It works like this in a JavaScript object. We can also use this character to style child elements with combinators

const Button = styled.button`
  padding: 10px;
  border: 2px solid red;
  border-radius: 4px;

  &:hover {
    border-color: blue;
  }

  & > span {
    display: block;
    font-size: 1.1rem;
  }
`;

const Example3 = () => {
  return (
    <main>
      <Button>
        <span>An example</span>
      </Button>
    </main>
  );
};
Enter fullscreen mode Exit fullscreen mode

Applying Media queries

Media queries are inescapable for large projects, and so you should be familiar with using them in styled-components. Each component will have to have its own media queries. I honestly like this approach because it just separates concerns and lets me focus on where I have a problem during maintenance or development.

Here is an example of using media queries in styled components

const Header = styled.header`
  padding: 10px;
  margin: 0 auto;

  @media (min-width: 768px) {
    margin: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;

    ul > li {
      display: inline-block;
      margin: 0 4px;
    }
  }
`;

const Example3 = () => {
  return (
    <Header>
      <h2>Ages Blog</h2>
      <ul>
        <li>Home</li>
        <li>About</li>
        <li>Contact</li>
      </ul>
    </Header>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the media queries, you don't have to explicitly specify a selector for the parent element, the styles that comes immediately after (without selectors and braces) the open brace of the media query is applied to the parent element which in our case is header.

Breakpoints can be applied dynamically in our media queries. Let's say you wanna set a different breakpoint when a user clicks a button or when something else is updated, you can pass the breakpoint as a prop to the styled component and access it as you would access any other props in styled-components. For example,

const Header = styled.header`
  padding: 10px;
  margin: 0 auto;

  @media (min-width: ${(props) => (props.active ? '920px' : '768px')}) {
    margin: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;

    ul > li {
      display: inline-block;
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Styled components props

Passing props

Earlier we had a LinkButton component that is an a element. To make this a working link we would need an href attribute. Well, we can simply pass an href props to the LinkButton component and have styled-components apply it to our anchor tag as an attribute. We would have

const LinkButton = styled.a`
  padding: 10px;
  border: 2px solid red;
  border-radius: 4px;
`;

const Example5 = () => {
  return (
    <main>
      <LinkButton href='https://example.com'>An example</LinkButton>
    </main>
  );
};
Enter fullscreen mode Exit fullscreen mode

This is the same thing for every other styled component. As long as the props you pass into the component are valid props for that element (the element the component is parsed into), it will work fine. Note that it will not throw an error if you pass an invalid prop, but it will simply have no effect.

For example, passing an href prop to a styled component that is an input element will have no effect. Let's see one more example of passing props

const Input = styled.input`
  padding: 10px 15px;
  border: 2px solid violet;
  border-radius: 2px;
`;

const Example6 = () => {
  return (
    <div>
      <h2>Fill the form</h2>
      <Input
        type='text'
        placeholder='Enter name'
        onChange={() => doSomething()}
        required
      />
      <Input
        type='password'
        placeholder='Enter password'
        maxLength={16}
        minLength={8}
      />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

You would notice the two input fields will be rendered differently, the first being rendered as a text that is required and with an event listener, and the second rendered as a password that its field (what the user types in) by default are not visible in the browser.

Accessing props

Apart from being able to pass props, we can also access props in styled components. It works just the same way it works in regular components. Say we have a unique bot that when activated with a button should have a different background color for that button. First, we would need to pass the active state as a prop to the Button component or whatever component that styles the button element.

const Example7 = () => {
  const [active, setActive] = useState(false);

  return (
    <div>
      {active && <h2>I have been activated</h2>}
      <Button onClick={() => setActive(!active)} active={active}>
        Activate Bot
      </Button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now we have that done, we would need to access it. In regular components, the props are passed as arguments to the component function, so we can access it globally in the component as a parameter. In styled-components, it is a little different, to access props passed into our styled components, we will have to create a function within our components and access the props as a parameter. So we would have

const Button = styled.button`
  padding: 10px;
  background: ${(props) => (props.active ? 'lightblue' : 'orange')};
  border: 2px solid purple;
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

Whenever we create a function within a styled component we have access to the props passed into that component through that function. We could have multiple functions - as many as needed in the component.

const Button = styled.button`
  padding: 10px;
  background: ${(props) => (props.active ? 'lightblue' : 'orange')};
  border: 2px solid ${(props) => props.borderColor};
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

If you're confused about how we were able to create a function within a string or how any of that is a function, then I must welcome you to 2022 where anything is possible with JavaScript :). Alright, jokes aside, the release of ES6 (ECMA2015) brought about template literals (\), a way of writing expressions in strings using the ${} to wrap the expressions.

Also with ES6, we can now create functions without the function keyword, instead, we use arrows (=>), thereby termed arrow functions. With arrow functions, we can write functions in one line without the return keyword or braces ({}) around it. You can learn more about arrow functions in MDN.

Creating and updating props

Interestingly, the props we want in our styled components can be created and updated inside within the component. So let's say you wanna override default props passed into a component or create one in the styled component, you'd need to use the .attrs() method. It takes just one argument of an object that will be merged with the styled component's props

const Button = styled.button.attrs({
  borderColor: 'orange',
})`
  padding: 10px;
  background: ${(props) => (props.active ? 'blue' : 'red')};
  border: 2px solid ${(props) => props.borderColor};
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

We can also attach some dynamic props values based on some conditions

const Button = styled.button.attrs((props) => ({
  borderColor: props.active ? 'orange' : props.borderColor,
}))`
  padding: 10px;
  background: ${(props) => (props.active ? 'blue' : 'red')};
  border: 2px solid ${(props) => props.borderColor};
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

Inheritance

Styled components can inherit styles from other styled components. Inheriting styles give you the flexibility of improving your app styles without recreating what already exists or filling up your styled component with so many props for conditionals. This is what I mean, say we had a Button component for our app, but we wanted a secondary button with a little style change i.e

const Button = styled.button`
  width: ${(props) => (props.secondary ? '130px' : '80px')};
  padding: 10px;
  background: ${(props) => (props.secondary ? 'blue' : 'red')};
  border: 2px solid ${(props) => (props.secondary ? 'red' : 'blue')};
  border-radius: 4px;
`;
Enter fullscreen mode Exit fullscreen mode

Or you could use .attrs. This get's a lot overwhelming when more differences are to be applied to the two buttons, or when the secondary button just happened to have a child element. The best solution at hand is inheritance.

Inheriting the styles from a styled component is as easy as passing the styled component as an argument to styled.

const Button = styled.button`
  display: block;
  margin: 10px;
  width: 80px;
  padding: 10px;
  background: transparent;
  border: 2px solid blue;
  border-radius: 4px;
  text-align: center;
`;

const LinkButton = styled(Button)`
  text-decoration: none;
  background: #ccc;
  color: #000;
`;

const SecondaryButton = styled(Button)`
  width: 130px;
  border-color: red;
  background: paleblue;
`;
Enter fullscreen mode Exit fullscreen mode

These are two use-cases of inheriting our main Button styled component. You should note that the LinkButton component will not be a link element (a). We will need the as props to specify what element we want it to be

const Example8 = () => {
  return (
    <header>
      <ul>
        <li>
          <LinkButton as='a' href='/'>
            Home
          </LinkButton>
        </li>
        <li>
          <LinkButton as='a' href='/about'>
            About
          </LinkButton>
        </li>
      </ul>
      <SecondaryButton>Get Started</SecondaryButton>
    </header>
  );
};
Enter fullscreen mode Exit fullscreen mode

When it comes to inheritance, props are also inherited from the parent styled component. But updates made to child styled components (i.e styled components that are inheriting a styled component) props will override that of the parents.

const Input = styled.input`
  padding: 10px;
  border: 2px solid orange;
`;

const UglyInput = styled(Input)`
  background: #000;
  color: #fff;
`;

const PasswordInput = styled(Input).attrs({
  type: 'password',
})`
  border: 2px solid red;
`;

const Example9 = () => {
  return (
    <form>
      <Input />
      <UglyInput />
      <PasswordInput />
    </form>
  );
};
Enter fullscreen mode Exit fullscreen mode

By default, the text is selected as the input type if not specified. So the type of text will be inherited by all of its inheriting styled component, that's why UglyInput has its type as text. But the case is different for PasswordInput as the prop type has been overridden with password, and now the browser treats it as a password field as it is.

This is just to illustrate prop inheritance, you really would not need to do this in a real-world scenario, instead, this is what you'd have

const Input = styled.input`
  padding: 10px;
  border: 2px solid orange;
`;

const UglyInput = styled(Input)`
  background: #000;
  color: #fff;
`;

const PasswordInput = styled(Input)`
  border: 2px solid red;
`;

const Example10 = () => {
  return (
    <form>
      <Input type='text' />
      <UglyInput type='text' />
      <PasswordInput type='password' />
    </form>
  );
};
Enter fullscreen mode Exit fullscreen mode

I prefer to explicitly set my types as props in the component rather than the previous example. Using .attrs is useful but I wouldn't use it if there is a much more readable approach.

Moving on, one thing you should have noticed about inheritance is that we basically created a component (a styled one) and then applied a new style to it. From the example above Input is a component, and we literally brought all of the styles and props in it into a new component.

Does this mean, I can create a component (not a styled component) and style it? Yeah, that's exactly what it means. How cool this is!

const HeaderComp = ({ className, title }) => {
  return (
    <header className={className}>
      <h2>{title}</h2>
    </header>
  );
};

const StyledHeaderComp = styled(HeaderComp)`
  padding: 10px;
  background: #000;
  color: #fff;
  text-align: center;
`;

const Example11 = () => {
  return <StyledHeaderComp title='A Unique Title' />;
};
Enter fullscreen mode Exit fullscreen mode

You must pass in the className prop in the parent element of the component to be styled because with it styled component can apply the given styles to the component. Apart from custom components, you can also style components that you did not create, maybe components from a module you installed, e.g the Image/Link component from Next.js. But with these components, you don't have to worry about passing the className as it is handled by default.

Animations

Animations in styled-components is a lot similar to what we have in CSS. In styled-components, we have access to a keyframes function that we can assign the value of animating an element to a variable and use this variable in the element's animation property.

In summary,

import styled, { keyframes } from 'styled-components';

const slide = keyframes`
  0% { transform: translateX(0) }
  50% { transform: translateX(100%) }
  100% { transform: translateX(0) }
`;

const MovingButton = styled.button`
  padding: 10px;
  background: #f4f4f4;
  border: 2px solid red;
  border-radius: 4px;
  animation: ${slide} 2s ease-in-out infinite;
`;

const Example12 = () => {
  return <MovingButton>I'm moving</MovingButton>;
};
Enter fullscreen mode Exit fullscreen mode

As easy as that. The only difference with CSS is that the keyframes is a function. One cool advantage of styled-components animations is that they are reusable. You can use the slide animation for some other component or element. In fact, this is an advantage in all of styled-components; being reusable.

Theming

With styled-components you can organize the styles/theme of your entire project. Setting up variables like sizes, colors, font families has been a great help in following a style guide for projects in CSS. The same thing applies in styled-components, only that styled-components makes it a lot better, and usable anywhere in your project.

All of your styled components for a project should not go into one file, as this isn't a good practice, I will show you how I organize mine. If all your styled components were to be in one file, theming like in CSS would simply require you to create an object variable and add the props you need, like colors, sizes, etc. like

const theme = {
  colors: {
    primary: '#333',
    secondary: '#fff',
  },
};

const StyledComp = styled.div`
  background: ${theme};
`;
Enter fullscreen mode Exit fullscreen mode

But if there are going to be multiple files containing your styled components, you may want to be tempted to have a global theme object variable and export it into all your styled component files. This is just tedious and a waste of tools.

Styled-components in its generosity offers a context provider, ThemeProvider so we can wrap around our app and pass in the theme properties we need for our app. This gives us the flexibility of using any of our theme properties in any of our styled components without importing or exporting.

Now, all we need do is import the ThemeProvider from styled-components and wrap it around our app with our theme properties in file App.js.

import { ThemeProvider } from 'styled-components';

const App = () => {
  return (
    <ThemeProvider
      theme={{
        colors: {
          primary: 'orange',
          secondary: 'blue',
          background: '#ccc',
        },
      }}
    >
      {/* our app components */}
    </ThemeProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

There is a theme prop that comes with ThemeProvider, it lets us pass in the theme properties of our app in it. For this, I am just using colors only, you could have more like font families, sizes, breakpoints (for media queries).

The theme prop is passed as a prop to all of our styled components that are children to the React App component by default. So accessing it will be like accessing any other props

const Button = styled.button`
  padding: 10px;
  border: 2px solid ${(props) => props.theme.colors.primary}
  background: ${(props) => props.theme.colors.secondary}
`;
Enter fullscreen mode Exit fullscreen mode

The theme prop passed into the ThemeProvider is used as a state in the app, and as such, changes to it will cause your app to rerender and update accordingly. An advantage of this rerendering is that we can dynamically set our theme properties and have all the styled components that use it updated.

With this, we can easily create a dark or light theme right in the theme object. This is how the object would be

import { ThemeProvider } from 'styled-components';

const Example13 = () => {
  const [darkTheme, setDarkTheme] = useState(false);

  return (
    <ThemeProvider
      theme={{
        colors: {
          primary: darkTheme ? '#000' : 'purple',
          secondary: darkTheme ? 'skyblue' : '#3caf50',
        },
      }}
    >
      <button onClick={() => setDarkTheme(!darkTheme)}>Toggle Theme</button>
    </ThemeProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

From the example above, the theme object will only be relevant used by styled components inside the Example13 component. If you want it to be global you can add it in your React App component (the main parent component).

The Global Styles

Oftentimes, we have styles that need to be applied globally to avoid repetition, for example, you could want that all elements should be a border-box, rather than repeating it over and over for each element, we would say in CSS

* {
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Another example could be removing all underline from a tags, applying different specific font-family on p and h1-h6 tags, or applying a custom scrollbar for your web pages, and many others. To apply these styles in styled-components is simple, we simply create a GlobalStyles styled component and apply it to our app once.

To create the GlobalStyles (you can give it any other name) we would need the createGlobalStyle function from styled-components.

import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
  * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    scroll-behavior: smooth;
  }

  body {
    font-size: 0.85rem;
    background: #fff;
    margin: 0;
    letter-spacing: 0.07em;
  }

  ::-webkit-scrollbar {
    width: 6px;
    height: 5px;
  }

  ::-webkit-scrollbar-corner {
    height: 0;
  }

  ::-webkit-scrollbar-track {
    background-color: transparent;
    border-radius: 25px;
  }

  ::-webkit-scrollbar-thumb {
    background-color: lightblue;
    border-radius: 25px;
  }
`;

export default GlobalStyles;
Enter fullscreen mode Exit fullscreen mode

Now we would head up to index.js (the main/root file of react), and use it there

import GlobalStyles from 'wherever-it-is.js'

...
ReactDOM.render(
  <React.StrictMode>
    <GlobalStyles />
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
...
Enter fullscreen mode Exit fullscreen mode

Organizing your project

What styled-components will not do for you is structure your files. Structuring in styled-components can take different forms, you can decide to keep all styled components in the same file as the component that uses it - just like in React Native. Or you could have all the styled components of a page in a separate file and import them as needed. No matter the case, try not to put all of your styled components in one file.

For me, I like separating my styled components into different files. Each page/component that requires a styled component will have to have its own file of styled components. For example

|___ index.js - a page
|
|___ Index.styled.js - a file that contains all styled components for the page index.js
Enter fullscreen mode Exit fullscreen mode

The way I structure my app with styled-components is inspired by Traversy Media's styled-components crash course. Here is a sample

Project
|
|___ pages
|     |
|     |___ index.js
|     |___ about.js
|
|___ components
|     |
|     |___ Header.js
|     |___ Footer.js
|     |___ styles
|            |
|            |___ Header.styled.js
|            |___ Index.styled.js
|            |___ About.styled.js
|            |___ Footer.styled.js
Enter fullscreen mode Exit fullscreen mode

Conclusion

So these are the basic things you need to get started with styled-components. Here is a blog and source that demonstrates all of what we have learned here today. It's a simple minimal blog.

If you feel all these wouldn't make you use styled-components, then this will. Styled-components applies prefixing to each style declaration that requires prefixing to be compatible with multiple browsers. All you have to do is write it to the current standard and styled-components will make it compatible with multiple browsers by applying prefixes specific to these browsers. So you don't have to worry about moz-, webkit- all of these are taken care of.

The idea of styled-components is to live a life free of "untraceable" class names. I mean it's not a must to use it, but if you think styled-components is a good fit for your project, then you should get started on it. To get started, I recommend you code along with this styled-components crash course by Brad, where you'll build an HTML template.

If you're using VSCode, I have made a gist of snippets for you to add to your javascript.json for styled-component. It contains what you need to generate a new styled component (stc/btc), inherited styled component (ibtc). Kindly ignore or change the commands :).

Thanks for reading. If you have anything to add or correct me about on this please don't hesitate to share in the comments section. Also hit me up on Twitter (@elijahtrillionz), let's connect.

Buy me a coffee

Discussion (2)

Collapse
nhatcapdang profile image
Nhatcapdang

I knew about this for a long time but still didn't understand well, until today. This's awesome, thanks

Collapse
elijahtrillionz profile image
Elijah Trillionz Author

Am glad 😊 you found it useful.
Happy hacking