Today I learned

Writing CSS-in-JS declaratively

A look at two different writing styles for CSS-in-JS: imperative and declarative

styled-jsx lets developers write CSS-in-JS in a more declarative fashion compared to other CSS-in-JS solutions. In my opinion, it leads to code that's easier to understand.

Before I tell you why I prefer styled-jsx, let's learn about some programming conceptsIt'll all make sense later, trust me!

Next: What does it mean to write 'more declarative' code?

Imperative vs. declarative code

Approach 1

There are two styles of expressing data. The most common way is to simply write it out as a data structure. We can call this a declarative style of writing, where we try to express logic without providing any instructions.

declarative-example.json
{
  "name": "my-js-package",
  "description": "A sample package",
  "author": "Rico Sta. Cruz"
}

Approach 2

There are cases when this style may not be flexible enough, so some systems take an imperative approach. In contrast to the declarative style, it's a piece of code with instructions that runs procedurally. Here's an example of a Ruby gem specification, which defines a Ruby package's metadata.

imperative-example.rb
Gem::Specification.new do |spec|
  spec.name = 'my-ruby-gem'
  spec.summary = 'A sample package'
  spec.authors = ['Rico Sta. Cruz']
end

You can do the same thing above with imperative code.

Next: What's the difference?

But they look the same!

Ruby's gem specification style is imperative because we're issuing instructions that are to be ran sequentially. It can keep variables, call functions, and do all the things you can do in a Ruby program. Think of the Ruby gemspec as a program talking to the system:

# Step 1: Okay, computer. Build a new Gem specification.
Gem::Specification.new do |spec|

  # Step 2: Set the name to 'my-ruby-gem'.
  spec.name = 'my-ruby-gem'

  # Step 3: Set the summary to 'A sample package'.
  spec.summary = 'A sample package'
  spec.authors = ['Rico Sta. Cruz']

# Step 4: Finish building.
end

On the other hand, JavaScript's declarative style isn't concerned with any instructions or control flow. It's not a piece of code that talks to your system, it's really just a table of keys and values.

Next: What makes imperative-style different?

Imperative is powerful

Imperative style gives you the power to write expressions that you can't easily do with a declarative-style conventions. For instance, here's an example gem specification where the list of files are gathered via a system command (git ls-files).

Gem::Specification.new do |spec|
  spec.name = 'my-gem'
  spec.summary = 'This is my gem'
  spec.files = `git ls-files -z`.split("\x0")
end
Next: What makes declarative-style different?

Declarative is straightforward

In contrast, here's how it may be declaratively defined in a JavaScript package's package.json. JavaScript's declarative approach may be less flexible than the imperative one, but the constraints of a JSON format makes things more predictable and easier to glance.

{
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "redux": "^4.0.1"
  }
}

No instructions here. The package.json format is really just a list of things, there are no imperative instructions to be executed.

Next: What does this have to do with CSS?

CSS is declarative

CSS is beautifully declarative. To write CSS is to write a list of rules, not a set of instructions. For instance, we would say buttons are supposed to be blue, rather than turn all buttons to blue.

button {
  background: blue;
}

Declarative: Writing "buttons are supposed to be blue" in CSS.

const buttons = document.querySelectorAll('button')

Array.from(buttons).forEach((button) => {
  button.style.background = 'blue'
})

Imperative: Writing "please turn all buttons to blue" in JavaScript. (Please don't do this.)

Next: What about CSS-in-JS?

A lot of CSS-in-JS is imperative

At first glance, we can say that most CSS-in-JS solutions require you to write in a declarative style, since it's mostly just taking CSS and putting it in JavaScript. Here's how you would define a blue button using styled-components:

const Button = styled.a`
  background: blue;
`

Declarative CSS-in-JS: No imperative logic here, right?

Where it breaks down

However, how would you start making red danger buttons? With styled-components, you'll have to provide a function which returns a CSS fragment depending on how you would interpret props:

const Button = styled.a`
  background: blue;

  ${(props) =>
    props.danger &&
    css`
      background: red;
    `};
`

Using styled-components: Using some JavaScript logic to generate some CSS. That is, CSS-in-JS-in-CSS-in-JS.

Interwoven CSS and JavaScript

We're now mixing the declarative nature of CSS with some rules that are written in an imperative style. We're also now interweaving 2 languages togetherCSS and JavaScriptwhere your brain may have to switch contexts mid-way.

const Button = styled.a`
  background: ${(props) => (props.danger ? 'red' : 'blue')};
  opacity: ${(props) => (props.isHidden ? 0 : 1)};
`
Next: Let's look at how styled-jsx solves this.

Declarative CSS-in-JS

No props logic required

styled-jsx lets me write CSS in a declarative fashion. It also minimizes the interweaving of CSS and JavaScript code. This, in my opinion, makes styled-jsx code easier-to-understand, even at the modest cost of a little extra verbosity.

How would you style a danger button differently in styled-jsx? Just use CSS classes as you normally would. Simple!

const Button = ({ children, danger }) => {
  return (
    <a className={`button ${danger ? 'danger' : ''}`}>
      {children}
      <style jsx>{style}</style>
    </a>
  )
}

const style = css`
  .button {
    background: blue;
  }

  .button.danger {
    background: red;
  }
`

Logicless CSS-in-JS. In this example, we simply declare a class rule with .button.danger, just as we would with regular CSS. There's no need for the props to be parsed from the CSS block.


Epilogue

Since writing this article, I've decided that I like the simpler approach of just using CSS modules. The reasons for that is the same as what's in this article: it's as declarative as you can get, and it's compatibility with existing tools is so far unbeatable.

You have just read Writing CSS-in-JS declaratively, written on November 08, 2018. This is Today I Learned, a collection of random tidbits I've learned through my day-to-day web development work. I'm Rico Sta. Cruz, @rstacruz on GitHub (and Twitter!).

← More articles