The Many Ways to Include CSS in JavaScript Applications

Avatar of Dominic Magnifico
Dominic Magnifico on

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

Welcome to an incredibly controversial topic in the land of front-end development! I’m sure that a majority of you reading this have encountered your fair share of #hotdrama surrounding how CSS should be handled within a JavaScript application.

I want to preface this post with a disclaimer: There is no hard and fast rule that establishes one method of handling CSS in a React, or Vue, or Angular application as superior. Every project is different, and every method has its merits! That may seem ambiguous, but what I do know is that the development community we exist in is full of people who are continuously seeking new information, and looking to push the web forward in meaningful ways.

Preconceived notions about this topic aside, let’s take a look at the fascinating world of CSS architecture!

Let us count the ways

Simply Googling “How to add CSS to [insert framework here]” yields a flurry of strongly held beliefs and opinions about how styles should be applied to a project. To try to help cut through some of the noise, let’s examine a few of the more commonly utilized methods at a high level, along with their purpose.

Option 1: A dang ol’ stylesheet

We’ll start off with what is a familiar approach: a dang ol’ stylesheet. We absolutely are able to <link> to an external stylesheet within our application and call it a day.

<link rel="stylesheet" href="styles.css">

We can write normal CSS that we’re used to and move on with our lives. Nothing wrong with that at all, but as an application gets larger, and more complex, it becomes harder and harder to maintain a single stylesheet. Parsing thousands of lines of CSS that are responsible for styling your entire application becomes a pain for any developer working on the project. The cascade is also a beautiful thing, but it also becomes tough to manage in the sense that some styles you — or other devs on the project — write will introduce regressions into other parts of the application. We’ve experienced these issues before, and things like Sass (and PostCSS more recently) have been introduced to help us handle these issues

We could continue down this path and utilize the awesome power of PostCSS to write very modular CSS partials that are strung together via @import rules. This requires a little bit of work within a webpack config to be properly set up, but something you can handle for sure!

No matter what compiler you decide to use (or not use) at the end of the day, you’ll be serving one CSS file that houses all of the styles for your application via a <link> tag in the header. Depending on the complexity of that application, this file has the potential to get pretty bloated, hard to load asynchronously, and render-blocking for the rest of your application. (Sure, render-blocking isn’t always a bad thing, but for all intents and purposes, we’ll generalize a bit here and avoid render blocking scripts and styles wherever we can.)

That’s not to say that this method doesn’t have its merits. For a small application, or an application built by a team with less of a focus on the front end, a single stylesheet may be a good call. It provides clear separation between business logic and application styles, and because it’s not generated by our application, is fully within our control to ensure exactly what we write is exactly what is output. Additionally, a single CSS file is fairly easy for the browser to cache, so that returning users don’t have to re-download the entire file on their next visit.

But let’s say that we’re looking for a bit more of a robust CSS architecture that allows us to leverage the power of tooling. Something to help us manage an application that requires a bit more of a nuanced approach. Enter CSS Modules.

Option 2: CSS Modules

One fairly large problem within a single stylesheet is the risk of regression. Writing CSS that utilizes a fairly non-specific selector could end up altering another component in a completely different area of your application. This is where an approach called “scoped styles” comes in handy.

Scoped styles allow us to programmatically generate class names specific to a component. Thus scoping those styles to that specific component, ensuring that their class names will be unique. This leads to auto-generated class names like header__2lexd. The bit at the end is a hashed selector that is unique, so even if you had another component named header, you could apply a header class to it, and our scoped styles would generate a new hashed suffix like so: header__15qy_.

CSS Modules offer ways, depending on implementation, to control the generated class name, but I’ll leave that up to the CSS Modules documentation to cover that.

Once all is said and done, we are still generating a single CSS file that is delivered to the browser via a <link> tag in the header. This comes with the same potential drawbacks (render blocking, file size bloat, etc.) and some of the benefits (caching, mostly) that we covered above. But this method, because of its purpose of scoping styles, comes with another caveat: the removal of the global scope — at least initially.

Imagine you want to employ the use of a .screen-reader-text global class that can be applied to any component within your application. If using CSS Modules, you’d have to reach for the :global pseudo selector that explicitly defines the CSS within it as something that is allowed to be globally accessed by other components in the app. As long as you import the stylesheet that includes your :global declaration block into your component’s stylesheet, you’ll have the use of that global selector. Not a huge drawback, but something that takes getting used to.

Here’s an example of the :global pseudo selector in action:

// typography.css
:global {
  .aligncenter {
    text-align: center;
  }
  .alignright {
    text-align: right;
  }
  .alignleft {
    text-align: left;
  }
}

You may run the risk of dropping a whole bunch of global selectors for typography, forms, and just general elements that most sites have into one single :global selector. Luckily, through the magic of things like PostCSS Nested or Sass, you can import partials directly into the selector to make things a bit more clean:

// main.scss
:global {
  @import "typography";
  @import "forms";
}

This way, you can write your partials without the :global selector, and just import them directly into your main stylesheet.

Another bit that takes some getting used to is how class names are referenced within DOM nodes. I’ll let the individual docs for Vue, React, and Angular speak for themselves there. I’ll also leave you with a little example of what those class references look like utilized within a React component:

// ./css/Button.css

.btn {
  background-color: blanchedalmond;
  font-size: 1.4rem;
  padding: 1rem 2rem;
  text-transform: uppercase;
  transition: background-color ease 300ms, border-color ease 300ms;

  &:hover {
    background-color: #000;
    color: #fff;
  }
}

// ./Button.js

import styles from "./css/Button.css";

const Button = () => (
  <button className={styles.btn}>
    Click me!
  </button>
);

export default Button;

The CSS Modules method, again, has some great use cases. For applications looking to take advantage of scoped styles while maintaining the performance benefits of a static, but compiled stylesheet, then CSS Modules may be the right fit for you!

It’s worth noting here as well that CSS Modules can be combined with your favorite flavor of CSS preprocessing. Sass, Less, PostCSS, etc. are all able to be integrated into the build process utilizing CSS Modules.

But let’s say your application could benefit from being included within your JavaScript. Perhaps gaining access to the various states of your components, and reacting based off of the changing state, would be beneficial as well. Let’s say you want to easily incorporate critical CSS into your application as well! Enter CSS-in-JS.

Option 3: CSS-in-JS

CSS-in-JS is a fairly broad topic. There are several packages that work to make writing CSS-in-JS as painless as possible. Frameworks like JSS, Emotion, and Styled Components are just a few of the many packages that comprise this topic.

As a broad strokes explanation for most of these frameworks, CSS-in-JS is largely operates the same way. You write CSS associated with your individual component and your build process compiles the application. When this happens, most CSS-in-JS frameworks will output the associated CSS of only the components that are rendered on the page at any given time. CSS-in-JS frameworks do this by outputting that CSS within a <style> tag in the <head> of your application. This gives you a critical CSS loading strategy out of the box! Additionally, much like CSS Modules, the styles are scoped, and the class names are hashed.

As you navigate around your application, the components that are unmounted will have their styles removed from the <head> and your incoming components that are mounted will have their styles appended in their place. This provides opportunity for performance benefits on your application. It removes an HTTP request, it is not render blocking, and it ensures that your users are only downloading what they need to view the page at any given time.

Another interesting opportunity CSS-in-JS provides is the ability to reference various component states and functions in order to render different CSS. This could be as simple as replicating a class toggle based on some state change, or be as complex as something like theming.

Because CSS-in-JS is a fairly #hotdrama topic, I realized that there are a lot of different ways that folks are trying to go about this. Now, I share the feelings of many other people who hold CSS in high regard, especially when it comes to leveraging JavaScript to write CSS. My initial reactions to this approach were fairly negative. I did not like the idea of cross-contaminating the two. But I wanted to keep an open mind. Let’s look at some of the features that we as front-end-focused developers would need in order to even consider this approach.

  • If we’re writing CSS-in-JS we have to write real CSS. Several packages offer ways to write template-literal CSS, but require you to camel-case your properties — i.e. padding-left becomes paddingLeft. That’s not something I’m personally willing to sacrifice.
  • Some CSS-in-JS solutions require you to write your styles inline on the element you’re attempting to style. The syntax for that, especially within complex components, starts to get very hectic, and again is not something I’m willing to sacrifice.
  • The use of CSS-in-JS has to provide me with powerful tools that are otherwise super difficult to accomplish with CSS Modules or a dang ol’ stylesheet.
  • We have to be able to leverage forward-thinking CSS like nesting and variables. We also have to be able to incorporate things like Autoprefixer, and other add-ons to enhance the developer experience.

It’s a lot to ask of a framework, but for those of us who have spent most of our lives studying and implementing solutions around a language that we love, we have to make sure that we’re able to continue writing that same language as best we can.

Here’s a quick peek at what a React component using Styled Components could look like:

// ./Button.js
import styled from 'styled-components';

const StyledButton= styled.button`
  background-color: blanchedalmond;
  font-size: 1.4rem;
  padding: 1rem 2rem;
  text-transform: uppercase;
  transition: background-color ease 300ms, border-color ease 300ms;

  &:hover {
    background-color: #000;
    color: #fff;
  }
`;

const Button = () => (
  <StyledButton>
    Click Me!
  </StyledButton>
);

export default Button;

We also need to address the potential downsides of a CSS-in-JS solution — and definitely not as an attempt to spark more drama. With a method like this, it’s incredibly easy for us to fall into a trap that leads us to a bloated JavaScript file with potentially hundreds of lines of CSS — and that all comes before the developer will even see any of the component’s methods or its HTML structure. We can, however, look at this as an opportunity to very closely examine how and why we are building components the way they are. In thinking a bit more deeply about this, we can potentially use it to our advantage and write leaner code, with more reusable components.

Additionally, this method absolutely blurs the line between business logic and application styles. However, with a well-documented and well-thought architecture, other developers on the project can be eased into this idea without feeling overwhelmed.

TL;DR

There are several ways to handle the beast that is CSS architecture on any project and do so while using any framework. The fact that we, as developers, have so many choices is both super exciting, and incredibly overwhelming. However, the overarching theme that I think continues to get lost in super short social media conversations that we end up having, is that each solution has its own merits, and its own inefficiencies. It’s all about how we carefully and thoughtfully implement a system that makes our future selves, and/or other developers who may touch the code, thank us for taking the time to establish that structure.