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;
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;
`;
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');
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])
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>;
};
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;
`;
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;
`;
Where:
-
ComponentName
can be any name -
element
can be any supported JSX element -
css_property
represents a property name in CSS -
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;
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;
}
`;
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>
);
};
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>
);
};
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;
}
}
`;
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>
);
};
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>
);
};
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>
);
};
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;
`;
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;
`;
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;
`;
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;
`;
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;
`;
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;
`;
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>
);
};
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>
);
};
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>
);
};
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' />;
};
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>;
};
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};
`;
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>
);
};
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}
`;
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>
);
};
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;
}
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;
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')
);
...
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
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
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.
Top comments (5)
I wanted to try styled components this time and no article could have made me this much comfortable with the concepts and all. Thank you so much for this comprehensive yet easy to understand article. It took me just half a day to get myself comfortable with the library all thanks to you. Keep posting such articles in the times to come!
Glad it was useful
I knew about this for a long time but still didn't understand well, until today. This's awesome, thanks
Am glad š you found it useful.
Happy hacking
Hello, we are having some issues with styled components. I'm not sure if we are importing them wrong or what but our pages now have ~150KB of css added to the head file.
We need all of this CSS moved out into a separate file but everyone is telling me it's not possible with styled components. The problem we have now is that this is making our HTML page too large and we are no longer being indexed by google due to it.
Any one have a solution for this other than using tailwind or another solution? We have 800 components...