Academind Logo

Component Styling Options

Inline styles, styled components, CSS with BEM, CSS Modules - there are many styling options. Here are the best ones!

Created by Maximilian Schwarzmüller
#

What's Complex About React Styling?

So what's the thing about React styling? Why is it "special"?

When getting started with React, you often learn about one styling option first: Inline Styles. Of course, you can just add normal CSS files to your React projects and assign styles to your components with the help of those.

But besides the fact that you sometimes need to change styles dynamically (e.g. swap a class name), you often also want to scope styles to a certain component. After all, you do work with components to encapsulate (and then re-use) logic - why not do the same with styles?

Normal CSS is a problem in both cases, because it can neither be changed dynamically, nor can you scope it to a component. It is always global.

In this article (and the above video), I'll therefore dive into a couple of styling solutions that specifically help with the scoping part. Because dynamically changing styles comes down to dynamically adding/ removing class names to elements - something which then actually is fairly easy to achieve with vanilla CSS, too.

So read on to learn more about the different styling options!

#

Using Inline Styles

This is the styling options you learn about first when diving into React: Inline Styles.

This is how you apply inline styles to HTML elements in one of your React components:

const Button = props => {
return (
<button
style={{ border: '1px solid #ccc', background: 'blue', color: 'white' }}
>
{props.children}
</button>
)
}

This creates a <Button> component which should have a grey border, blue background and white text. All of that is achieved by assigning styles directly to the HTML element. It's basically the equivalent of assigning styles directly to an HTML element in a normal HTML page (i.e. outside of a React component).

<button style="border: 1px solid #ccc; background: blue; color: white">
Click Me
</button>

Therefore, inline styles also share a couple of the disadvantages of HTML inline styles, most importantly:

  • You work against the cascading nature of CSS - inline styles have highest specificity

  • All your style code sits right next to your markup / component code

  • Using pseudo-selectors / pseudo-elements is not possible - you can't add a :hover inline style

Inline styles also have an advantage though:

  • You do ensure that the styling is only applied inside of that component

So inline styles are not horrible (and ultimately it comes down to personal preference whether you want to use them or not) but the more style code you write, the harder it can become to re-use style code and separate JS logic from the styling code. Working together with global styles can also be tricky since inline styles always take highest priority in CSS.

#

Switching to Styled Components

You might be fine with not being able to re-use your code (after all, this article IS about scoping styles to components). Not using the cascading nature of CSS might also be okay - maybe you didn't plan on having global styles that should affect component styles anyways.

But not being able to style pseudo-selectors / -elements can be a real issue. On a button, you probably want to provide a :hover style.

That's where styled-components, a third-party library, enters the stage.

You can add it to your project with one simple command:

npm install --save styled-components

Side-note: I had an error thereafter and also needed to run:

npm install --save-dev @babel/runtime

Now you're ready to use this package. But what does it actually do?

styled-components gives you a way of writing HTML elements in a way that allows you to attach more powerful CSS to it. Here's an example:

import styled from 'styled-components'
const Button = styled.button`
background: blue;
color: white;
border: 1px solid #ccc;
`

This certainly looks strange at first. This styled.button-double-backtick syntax is not something you see often. It's a so-called tagged template - an official JavaScript feature.

In the end, JavaScript will call styled.button as a method and pass your template string + any interpolations you might have in there to that method. And the styled.button method is of course coming from the styled-components package. Internally, the method then returns a React component that wraps itself around the <button> element and assigns the provided styled to it.

This, however, does not happen with the help of inline styles!

Instead, styled-components extracts your CSS code out of the component and injects it into the loaded HTML page. It also generates a unique class name and adds that to the <button> element that was wrapped.

The advantage of this is, that you can write real CSS code whilst scoping it to a specific element (i.e. it's not affecting all buttons on the page in this example).

You can also work with pseudo-selectors:

import styled from 'styled-components'
const Button = styled.button`
background: blue;
color: white;
border: 1px solid #ccc;
:focus {
outline: none;
}
:hover {
background: lightblue;
color: blue;
}
`

Hence the styled-components package really takes React component styling to a new level - it's a bit like inline styles but with more features available.

You can learn all about the package in the official documentation;

#

Back to "Vanilla CSS" with BEM

Maybe you're like me and you're not the biggest fan of mixing JavaScript code with CSS code. In the previous approaches, we did that of course.

Or you're working in a bigger team and you're responsible for the JS logic but not for the CSS code. Since all the CSS code sits next to your component JS code, splitting work might therefore be tricky.

For reasons like these, you might be inclined to work with "real CSS" code, outsourced into .css files.

Fortunately, this is also an option when working with React. No one is stopping you from adding a Button.css file next to the Button.js file.

The Button.css file could look like this:

button {
background: blue;
color: white;
border: 1px solid #ccc;
}
button:focus {
outline: none;
}
button:hover {
background: lightblue;
color: blue;
}

The Button.js file could look like this:

import React from 'react'
import './Button.css'
const Button = props => {
return <button onClick={props.onClick}>{props.children}</button>
}

Thanks to Webpack (which is used by default in React projects created with create-react-app), you can import .css files into .js files. Webpack will see that import and resolve it correctly. It will inject the CSS code directly into the <head> of your HTML page and therefore apply it globally to the webpage.

The result of the above setup would be, that you have a correctly styled button.

But ... we have an issue. Potentially at least.

Everything generally works, but the styles are not scoped to the component. Any button, anywhere on the webpage will have these button styles. Not just the <button> in the Button component!

Maybe, that is what you want - everything's good in that case (though you might want to consider setting up such global styles in a different .css file to avoid confusion). But maybe you also want to scope the styles to this specific component.

This can be achieved with vanilla CSS by using class names and ensuring that you assign unique classes.

For example, in your .css file:

// highlight-next-line
.button {
background: blue;
color: white;
border: 1px solid #ccc;
}
// highlight-next-line
.button:focus {
outline: none;
}
// highlight-next-line
.button:hover {
background: lightblue;
color: blue;
}

Instead of using the generic button tag selector, we now use the more specific .button class selector. Only elements (no matter if they're a <button> or not) with a class of button will receive these styles now.

So in our component, we would now have to adjust the JSX code like that:

import React from 'react'
import './Button.css'
const Button = props => {
return (
<button
// highlight-next-line
className="button"
onClick={props.onClick}
>
{props.children}
</button>
)
}

Pretty good! Now other <button> elements anywhere else on the page will not receive these styles - unless they also have className="button" on them.

But we can still run into an issue here: We have to ensure that our class names are unique. If we accidentally re-assign styles to .button in some other .css file, we of course also affect our Button component. Because styles are applied globally! Always!

So we really have to ensure that we don't accidentally re-use class names.

To achieve this, we can use a CSS class naming pattern referred to as BEM (Block Element Modifier).

The idea behind BEM is that you name classes in a very explicit and structured way. Names like .modal__content--selected are common there - you basically compose class names of three parts:

  • The Block (modal) - the standalone entity that should be meaninful on its own

  • The Element (__content) - a part of the block

  • The Modifier (--selected) - a special state of the block element

You might not always have all parts in every class name. In the above example, we would be fine with just using .button. You can also learn more about BEM here.

Still, we have to ensure that we never repeat a class name - so maybe we can do better?

#

Vanilla CSS with a Twist: CSS Modules

Time for CSS Modules!

CSS modules take the task of selecting unique class names away from you by generating unique class names automatically. And the best thing is: Projects created with create-react-app have built-in support for CSS modules!

To use CSS modules, you just have to tweak the .css filename a bit.

Button.css becomes Button.module.css.

Also adjust the import in the Button.js file and use the auto-generated class names:

import React from 'react'
//highlight-next-line
import classes from './Button.module.css'
const Button = props => {
return (
<button
// highlight-next-line
className={classes.button}
onClick={props.onClick}
>
{props.children}
</button>
)
}

What's happening here?

CSS modules is in the end simply a tool that is part of the Webpack build workflow. When seeing a .module.css file and import, the CSS class names you defined get transformed to unique ones. For example .button could become .button__button-abc. Without you writing that code - you write .button in your .css file, that's all!

CSS modules transform your CSS class names and expose the transformed names in the classes object imported from Button.module.css.

You then access the generated, unique class name as a property on that classes object. The property name is the original CSS class name you defined in the .css file - in the above example, button.

So because we have

.button {
/* Some CSS Styles */
}

in Button.module.css, we use:

<button
// highlight-next-line
className={classes.button}
onClick={props.onClick}
>
{props.children}
</button>

in the Button.js file.

Hence you're now in a world where you write vanilla CSS code, separated from your JS code and you still have guaranteed component scoping.

#

Which One Is Best?

With all these approaches covered - which one should you go with?

Of course, it comes down to the scale of your project (are you working on it on your own or in a team) and your personal preferences.

My clear winner is the CSS Modules approach - but that of course does not mean that it has to be your favorite as well! I simply like separating CSS from JavaScript code.

Some people really love the css-in-js approach offered by styled-components and of course being able to define it all in one place can have some merits. It can also make conditional styling/ changing styles a bit easier (though you can always dump in some inline styles if you have some specific style that needs to change based on some condition).

In my opinion, inline styles should mostly be avoided. You have certain restrictions there (no pseudo selectors) and you "disable" the cascading nature of CSS.

So hopefully, this therefore was helpful - definitely leave some feedback (button at the beginning of the article) and watch the video that belongs to the article if you haven't already! :-)

Recommended Courses