Styled components - advanced topics

In the previous article I introduced you to styled-components. Today we will dive deeper into this topic. I will show you what else can be achieved using them.
In the article I will raise more advanced issues related to CSS, I will not describe them assuming that you already have an idea about them. I will show instead, how to use them in styled-components in conjunction with React. If the issues from the article are not known to you then peek into the links section at the end - surely you will find a few useful links there. Not prolonging, let's move!
Passing props
The styled-components library allows passing data from React components directly to style definitions. Thanks to this in styles we have access to dynamic values coming e.g. from the component state, which they concern. Example ways of using this technique can be:
- controlling visibility of a fragment;
- representing component activity for example: active object marked with green color, and inactive - red;
Below two examples of passing props and using them:
// Definition of components
<Styled.SquareButton size={100} onClick={onSquareButtonPressed}>
Push me!
</Styled.SquareButton>
<Styled.ToggledButton isVisible={isButtonVisible}>
And I will hide
</Styled.ToggledButton>
// Styles
export const SquareButton = styled.button`
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
`;
export const ToggledButton = styled.button`
display: ${(props) => (props.isVisible ? "block" : "none")};
`;
<br />
Note that in the case of SquareButton we directly use the passed "size" value to set the height and width of the square. In the definition of ToggledButton style however we check the variable and make the style dependent on its value. If it is true - we show the button, if it is false - we hide it.
Pseudoselectors, pseudoelements and nesting
Styled-components allows for using pseudoselectors and pseudoelements inside component style definition. The library supports SCSS syntax, which means that style definitions can be nested in a hierarchical way. I recommend using nesting only in two cases:
- when you are not able to otherwise influence the style provided by the UI library you use,
- when you create complex styles, which are difficult to define without creating a hierarchy (e.g. hover on parent changing style of child element).
Below you will find two examples illustrating the use of pseudo- and nesting:
export const Paragraphs = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
div {
background: antiquewhite;
:first-child {
border: 1px solid red;
}
p {
color: navy;
&.classy-paragraph {
color: brown;
}
&#unique-paragraph {
background: aliceblue;
}
}
}
`;
export const HoverableBox = styled(Box)`
transition: 300ms all;
background: aqua;
:hover {
background: aquamarine;
}
`;
Additional attributes passed to DOM
Styled-components has its own way of passing attributes to DOM elements. In HTML we would simply define a given attribute and give it a value. Here we define them using the “attrs” function. It can be treated as an advantage or disadvantage, for me it definitely is an advantage. Why disadvantage? Because unfortunately we have to remember to use the function. And why advantage? The biggest advantages I see is that we do not "litter" the render with attributes and that they will be passed to every element described by a given style. To illustrate it a bit - if I create PasswordInput, which will have assigned attribute “type” equal “password” then all PasswordInputs will have it - I do not have to repeat myself.
It is worth remembering that the attrs function in the parameter accepts props passed from the component. This means that we can create an input and using props control its type (similarly as we did in HTML).
There are two ways of specifying attributes of a styled-component:
The first of them involves defining a constant object describing attributes assigned to the DOM element.
export const PasswordInput = styled.input.attrs({
type: "password",
})``;
<br />
The second way uses the previously mentioned function.
<Styled.TestableBox testID={"test-box"}>
export const TestableBox = styled(Box).attrs((props) => ({
"data-testid": props.testID,
}))`
background: blanchedalmond;
`;
<br />
And here a screen showing what is the result generated in DOM:

Global styles
Another technique, which can turn out to be useful when creating applications is defining global styles. Using classic CSS it was childishly simple - defining a style by default it had global scope. In the case of styled-components styles are by default encapsulated, so that they were visible only within a given component.
To achieve the effect of global styles in we must define the style using the createGlobalStyle function. The next step is “rendering” them in any place of the application - usually the best location is the application entry component.
In the example below you will find pass passed “theme” prop - I will pass to it in the next paragraph, in which I will discuss creating color themes.
import { createGlobalStyle } from "styled-components";
export default createGlobalStyle`
body {
background: ${(props) => props.theme.background || "#fff"};
color: ${(props) => props.theme.color || "#333"};
}
h3 {
margin: 1rem;
}
`;
<br />
Inside any component:
import GlobalStyle from "./Globals/Globals";
<GlobalStyle />
Creating color themes
A common use case of cascading style sheets is creating themes allowing for quick switching between color versions. Using CSS it could be achieved by defining a class for each color variant and controlling them using JS.
Styled-components allows for achieving similar effect by providing ThemeProvider. It is a component providing a theme to all child components using React Context API.
The library provides several ways to create themes. I however will focus on - in my opinion - the simplest of them. If you want to read a bit more about the others, at the end of the article I will place a link to the source.
Moving to implementation - it is good to first define several themes, and then give the selected one of them to ThemeProvider as default. Then in the component containing ThemeProvider we can create state, which will change the selected theme. Theme change will be propagated to child components of ThemeProvider.
Below you will find sample usage, in which I change the background and font defined in the global style (such a combo, huh!).
Theme definitions:
export const LIGHT_THEME = {
background: "#fff",
color: "#333",
};
export const DARK_THEME = {
background: "#333",
color: "#fff",
};
<br />
Component containing ThemeProvider:
const [theme, setTheme] = useState(LIGHT_THEME);
...
<ThemeProvider theme={theme}>
<GlobalStyle />
</ThemeProvider>
<br />
Child component, which in props received setTheme, serving to change the theme:
<Styled.DarkThemeButton onClick={() => setTheme(DARK_THEME)}>
Dark theme
</Styled.DarkThemeButton>
<Styled.LightThemeButton onClick={() => setTheme(LIGHT_THEME)}>
Light theme
</Styled.LightThemeButton>
<br />
We have it!
That would be all from our short adventure with styled-components. I raised the most frequently used issues in work with the library. If you are further hungry for knowledge then you will find more issues in the official documentation, to which link you will find below.
I extended the example from the previous article with today's issues. If you want to tinker in its source code yourself then you will find it here. <br />
Useful links: