Icon of sun for "Light Mode" theme

Learn Enough Styled Components to be Dangerously Fabulous

Image of undefined by Alexas_Fotos by UnsplashImage of undefined by Alexas_Fotos by Unsplash

Photo by Alexas_Fotos on Unsplash

There are many different approaches you can take to writing CSS for your React Components. One of the more commonly used approaches is to use a CSS-in-JS library, and one of the more popular CSS-in-JS libraries is Styled Components (Note: throughout this post I often refer to Styled Components as “SC”). Some of the benefits of include:
  • You can scope your CSS directly to the elements that use it and not have to worry so much about unexpected style cascades or specificity rules (although keep in mind that inheritable CSS properties will still cascade to descendent components of your SCs, so the styles aren’t strictly scoped to just the element you’re applying your SC styles to, but this inheritable behavoir is often desirable).
  • SC uses tagged template literals to allow you to harness the power of JavaScript to dynamically apply styles based on state and props (you’ll see plenty of examples of this throughout this post).
  • It allows you to have your CSS colocated in the same part of your codebase as the code that uses it, which can make it much easier to maintain your CSS and make your code more reusable. It can also help with performance since you don’t have to uncessarily load a bunch of styles that your page doesn’t use.
  • The way that you use SCs aligns well with React’s component and composition-based model, and just like with React Components you can name your SCs anything you want, which can make your components more readable since you don’t always have to reference classes, ids and other element attributes to try and figure out an element is.
  • The way you write your style rules is, for the most part, the same as normal CSS, which helps with consistency and makes it easier to transition CSS rules to SC.
As cool as the Styled Components library is, when you first start learning it after only writing vanilla CSS it can be slightly awkward because you have to adjust your mental model from targeting everything in a pure CSS file with selectors like classes, to writing your styles as JavaScript and implementing the style as an actual component that's rendered directly inside of a React component. As you continue to read this post and learn SCs you may start noticing how some things look unusual, but just keep in mind that most of that unusual looking stuff is just plain JavaScript and you'll get used to it pretty quickly and start to see how powerful it can be.

Getting Started

Once you've installed Styled Components you can import it like so:
1import styled from ‘styled-components’
Now that you're ready to start using it, here’s the basic syntax for creating a Styled Component:
const <YourElementName> = styled.<elementType>`
<propertyX>: <valueX>;
<propertyY>: <valueY>;
`
// Notice how the CSS declarations inside of the
// backticks are the same format as vanilla CSS
Here’s an actual example of a real SC definition and its implementation inside a React component:
const BlueBox = styled.div`
width: 100px;
height: 100px;
background: blue;
`
// Once the SC has been defined you can use it inside
// of a React functional or class component in the same
// was as you would use ordinary React components inside
// of other React components:
function App() {
return (
<BlueBox />
)
}
Note how the BlueBox SC definition is up above the part of the React Component that actually renders the component (in this case, the App function). This is a common pattern and additionally it’s recommended that you never put your SCs inside of your React component render function. As you create more and more SCs inside of a given file it may start to seem like it's adding more noise and complexity to the file, but you'll get used to it before long and you can easily collapse the rules in your code editor to compress things a bit. If it's really bothering you, you can even define your SCs in a separate file to clean things up a bit (see the post by Sara Vieira that’s linked at the end of this post for an example).

Adapting Styles Based on Props

One of the more helpful and utilized features of Styled Components is that you can adapt and apply styles based on props that are passed to the SC. The props can come from anywhere in your app, but ultimately they must end up on the actual implementation of the Styled Component element (where the SC is used inside of the React component). The props passed to the SC implementation are automatically passed to the SC definition, and since the definition uses tagged template literals you can use JavaScript interpolation inside of the back-ticks to dynamically determine how your styles are applied.
info iconThe props that get passed to a Styled Component are different than the props that get passed to an actual React component, although any props that are passed to a React component can be used as the value of a prop that gets passed to a SC. I know this might be confusing to read right now but it’ll make sense as you see more examples below. I’d also recommend that once you start using SC that you try logging and comparing the props that you pass to a React component to the props that you pass to a SC within that React component.
Here is an example of one way you can adapt based on props:
const NavLink = styled.a`
// adapted/dynamic or non-adapted/static styles can go here...
// this shows how the style can be adpated based on props:
${( props ) => props.isActive && `
// in this example it’s probably unecessary for me to
// have both `isActive` and `color` props on this
// element since just `color` by itself would suffice,
// but I just included both to illustrate the pattern
color: ${props.myColor};
// more styles can go here if you want them to be
// applied when `isActive` is true
`}
// more styles can go here etc...
`
To pass props to the Styled Component definition above it’s the same type of syntax as normal React props:
<NavLink
isActive
myColor=’salmon’
/>
info iconStyled Components is typically smart enough to filter out non-standard HTML attributes (e.g. in the examples above isActive and myColor aren’t actual DOM attributes) so that they don’t end up getting rendered to the element in the DOM.
For the interpolation in the NavLink definition above, I have the full property/value declaration (color: ${props.myColor};) inside of the template literal back ticks, but you can also use patterns like the one below where just the values are being interpolated but the properties (color, font-size) are static:
const NavLink = styled.NavLink`
color: ${( props ) => props.isActive ? ‘salmon’ : ‘lightsalmon’};
font-size: ${( props ) => props.size === ‘large’ ?50px’ :25px’};
`
info iconDepending on how you’re using interpolations to adapt based on props, you may need to enclose the CSS property values in quotes. For instance, in the NavLink example if I didn’t enclose the words large, salmon, and lightsalmon in quotes, I would get an error because the program would be looking for variables of those names but they don’t exist. Same thing with the number units for the font-size property; if those weren’t in quotes I would get an error because variable names can’t start with a number.
As you may’ve noticed, you’ll often be using short-circuiting operators like &&, ||, and ternary statements to concisely control the logic for your interpolations. If the code within an interpolation evaluates to undefinednullfalse, or an empty string then the interpolation will be ignored by SC, while any other styles below the ignored rule won’t be affected. By using the || operator you can effectively set a default value. In this example, if myColor is passed to the SC it will use whatever that color is, otherwise it will fallback to salmon (for the sake of brevity just assume that all rules are within a Styled Component like NavLink from above):
color: ${( props ) => props.myColor || ‘salmon’};
By using the && operator you can short circuit so that if the initial expression is false, the corresponding value won’t be applied. In this example if the myColor props exists it will use that color, otherwise it will do nothing with the color:
color: ${( props ) => props.myColor && props.myColor};
The line above can be shortened to:
color: ${( props ) => props.myColor};
When writing interpolations you can destructure any props from the prop object to make things even more concise. For instance, if we refactor the example above to use destructuring we’d end up with this adorable little CSS declaration:
color: ${({ myColor }) => myColor};
Most of the interpolations that you write will probably be inline and concise as possible, but if you have more complex computational logic where you need or just prefer to write some code over multiple lines, or if you just want to locate the computational logic in a place other than inside a tempalte literal where the SC is defined, you can put the logic inside of a separate function and return the result of the function to a variable, and then you only use the variable in the interpolation. For instance, imagine that the following function expression is located up above the SC definition or maybe even exported from a completely different file:
const computeColor = () => {
// A bunch of calculations go here and eventually
// the return the final value to the `computeColor` variable
}
// Now within the corresponding SC definition you
// can simply insert the variable into the interpolation:
color: ${color};

Inheritance/Extending Styles

Since it’s always a good idea to keep your code reusable and DRY, you can inherit style rules from previously defined SCs and extend or overwrite those properties as needed. For instance, if we wanted to use the same properties as the BlueBox element from above but just give it a different background color and add a border, you could do this:
const SalmonBoxWithBorder = styled(BlueBox)`
background: salmon;
border: 1px solid lightsalmon;
`
Another API that allows you to reuse styles is the as prop, which allows you to easily change what type of element or Styled Component gets rendered, while keeping the styles from the original SC/element.

Pseudo Classes, Pseudo Elements, Combinators, and Nesting

Styled Components allows you to write the same pseudo classes, pseudo elements, and CSS combinators you're used to, and you can also write nested rules:
info iconThe weird thing you may notice about these examples is the use of the ampersand (&), which Styled Components uses as a placeholder for the parent style (if you’ve ever used a CSS pre-compiler like Sass, it’s similar to how ampersands are used there), so when the Styled Component gets converted into plain CSS it will replace any instances of & with the the outer Styled Component element it’s within. You can also have selectors without the ampersand preceeding it, and in this scenario Styled Components will target children of the outer Styled Component that have the corresponding selector (although I've found that for most common use-cases you'll probably be using the ampersand).
Here are some examples of pseudo classes and pseudo elements:
const Button = styled.button`
background: lightsalmon;
&:hover {
cursor: pointer;
background: salmon;
}
&:disabled {
cursor: not-allowed;
background: gray;
}
&::before, &::after { content: ' 👉 '}
// Just like in vanilla CSS, pseudo-elements can have
// two colons or one, although sometimes you’ll see one
// because it’s compatible with Internet Explorer 8
// and earlier, although if you’re using an IE version
// this old you don’t deserve psuedo-elements!!)
Here are some examples of nesting and CSS combinators.
const Div = styled.div`
// target any descendant elements of `Div` that have
// the `some-class` class
& .some-class { ... }
// target all `div` elements that have a class of
// `some-class` and are adjacent to `Div`
& + div.highlight { ... }
// target any `Div` element that is a direct child
// of `Div`
& > & { ... }
// target the first letter of each `p` that is a
// descendant of `Div`
& p::first-letter { ... }
// target `p` elements that are descendants of an
// element with the `some-class` class, where the
// element with `some-class` is child of `Div`
.some-class p { ... }
// target the last child Div of an element with
// the `some-class` class
.some-class & { ... }
`
You can even do some more advanced stuff like this section of the docs explains.
info iconJust like with vanilla CSS, spacing is important with Styled Components. For instance, & #some-id is not the same as &#some-id (no space between). The former will select any elements that are children of the & component (whatever that component/element may be) while the latter will only select instances of the & component that have the #some-id id attached to it.

Global Styles

Styled Components provides a helper function called createGlobalStyle that you can use to define global CSS rules. Keep in mind that you can also still define global rules with vanilla CSS, so usually you only need to use createGlobalStyle if you want global styles that use interpolation to adapt rules based on props.
import { createGlobalStyle } from 'styled-components'
const GlobalStyles = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
ul { list-style: none; }
a { text-decoration: none; }
// The rules above are static but this rule will adapt the color of paragraphs based on the // `theme` prop passed to the global style component
p { color: ${({ theme }) => theme.textColor}; }
`
// By implementing the global styles at the top level of your app, SC will automatically give all the other components (in this example NavBar, MainContent, Footer and all of the descendents of those components) access to it without you having to import the styles or do anything special
export default function App() {
return (
<React.Fragment>
// If you’re interested in implementing themes you should use Styled Components’ ThemeProvider, which I’m not using in this example just to keep things simpler
<GlobalStyles theme={{ textColor: textColorState }} />
<NavBar />
<MainContent />
<Footer />
</React.Fragment>
)
}

Media Queries

Media queries look the same in SC:
const Page = {
@media (max-width: 750px) {
font-size: .8em;
}
}
Not much to see here but I recommend reading this article by Corina Udrescu to see a cool way you can use SC and interpolations to manage your breakpoints.

Keyframes & Animations

SC provides a keyframes function helper that you can use for your animations:
import styled, { keyframes } from 'styled-components'
const fadeIn = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`
const FadeInButton = styled.button`
animation: 1s ${fadeIn} ease-out;
`
info iconAs you can see with the animation values above, when you have CSS properties that take multiple types of values (e.g. typically shorthand properties like animation, margin, border, etc.), you can mix interpolated values with static values on the same rule.

Specificity

When you’re working on a project that uses both vanilla CSS and Styled Components you may unexpectedly find that a rule in your vanilla CSS mistakenly has more specificity than your SC, and in this scenario if you’re not able to decrease the specificity of the vanilla CSS rule (maybe because there are elements elsewhere that depend on that rule) you can try adding ampersands to your SC rule to give it more priority:
const StyledDiv = styled.div`
&& { background: salmon; }
`
SC will then generate a CSS rule that’s equivalent to:
.StyledDiv.StyledDiv { background: “salmon”; }
The rule above results in a specificity of 20 since each class attribute has a total specificity weight of 10 (For more info on specificity see this article by Vitaly Friedman). If you needed it to be more than 20 you could add additional ampersands and possibly other CSS selectors that would increase the specificity to the desired amount. As with vanilla CSS, you should try to avoid using the !important in SC.

Styling Any React Component

Typically when you’re writing SCs you’ll be applying your CSS to a regular ol’ HTML element, whether it’s the element itself (e.g. styled.input) or another element that already has SCs applied to it that you are inheriting from (e.g. styled(MyStyledInput)), but you can also apply SCs to other React components that you’ve created or that you’re that you’re importing from a third-party library. To do so you just need to pass that element the className attribute so that SC knows that this is an element that it can apply it’s magic to. For instance:
function MyReactDiv(props) {
return (
<div className={props.className}>
This is my React component
</div>
)
}
const MyReactDivStyled = styled(MyReactDiv)`
color: salmon;
background: ${props => props.bgColor || ‘black’};
`
export default function App() {
return <MyCustomReactDivStyled bgColor=’midnightblue’/>
}

Debugging

If you try to inspect SC elements in Dev Tools you may notice that the elements have a cryptic, randomly generated class name like class='sc-bdvvaa jNWcO', which obviously isn’t very helpful when you’re trying to figure out what Styled Component in your codebase it corresponds to. If you used Create React App to setup your app you can easily get SC to generate more descriptive names that include the file name and SC name (e.g. class=”Nav_Navbar-sc-bdvvaa jNWcO”) by adding /macro to your SC import like: import styled from 'styled-components/macro'. If you’re not using Create React App you can still get these descriptive names by using SC’s Babel plugin, but I haven’t strayed from the CRA path yet so I can’t speak on the plugin at this point.

Conclusion & Further Reading

With the topics we’ve been over in this post you should have plenty of weapons to wield in the fight for fabulosity against the army of the Unstyled (I’m sorry, I don’t know how to close out blog posts), although there are many other tactics that Styled Components can arm you with so I recommend going through the docs, including the Advanced section (I primarily just discussed topics from the Basics section, but not everything). I also recommend these blog posts if you’re feeling particularly peckish for more: