react {
  css-in-js: overflow;
}
react::meta {
  author: Nick Ribal;
  date: June 2017;
}

Nick Ribal

image/svg+xml

Front-end consultant, freelancer and a family man

    @elektronik

Disclaimer

  • I ♥ CSS! Sadly, I know in person only one guy who knows CSS better than me

  • I've worked on:

    • Content sites

    • 3rd party widgets hosted on others sites

    • Single page applications (client and isomorphic)

    • Making of all the above play nice together

  • This isn't a "WUT CSS SUCKS LOL!!!" talk. I am suggesting new technologies - not a replacement of CSS. It's about adding new tools to your arsenal

  • I haven't actually used most of these tools, yet :(

  • I've been a heavyweight Sass user for a few years now

Topics we'll cover

  • Two examples of CSS ecosystem FAIL

  • Why it's like that

  • The long history (in front-end terms, lol) of css-in-js

  • Correlation to evolution of modern HTML authoring

  • How css-in-js solves CSS ecosystem deficiencies

  • css-in-js features which you can't have otherwise

  • Examples of popular css-in-js libraries with React integration

The trouble with CSS

...is not this

The main trouble with CSS

3a. Use simple <link rel=stylesheet href=hardcoded-CDN.css> just for the reset. Bad practices: another request, not an actual package you manage, not self-hosted

Example #1: "I need CSS reset/normalize"

CSS workflow is stuck in document mode: not modular, not interoperable, not scopable, optimizations aren't built-in. You have to work hard :(

Why is it so damn difficult?

1. Start new project

2. Add CSS reset/normalize

3. ???

4. PROFIT!!!

3b. Copy/paste into your CSS

4. ಠ_ಠ ???

3c. Use complex tooling to do achieve something fundamentally basic (CSS packages in npm are scarce and inconsistent)

Example #2:

"I want to reuse styling of an existing component (from another runtime)"

No easy way to do something which is so fundamentally simple :(

Good idea

Bad implementation

Why can't we easily reuse CSS?

Modern CSS frameworks aren't interoperable

Each is like a country: has it's own language, concepts, conventions, supporting different browsers

This results in incompatible monoliths, negating basic software development best practices: code sharing, modularity, reuse and huge variance in how the same features are implemented

This is fake old news...

In November 2014, Christopher "vjeux" Chedeau, who is working on React and React Native at Facebook, gave a thought-provoking talk titled "CSS in JS".

From the documentation of fela, a css-in-js library:

He outlined the problems of CSS at scale and later showed how they solved those at Facebook. They were able to solve most, but in return had to add a lot of additional tooling.

Then he introduced a complete new approach to handle those issues without any actual extra work. Using inline styles. It seemed crazy, but just worked magically.

@vjeux's talk created shockwaves and turmoil in the front-end community

It's all about best practices, which are there for good reasons!

But times are changing...

...and yesterday's best practices stop being best, since they fail to serve their purpose

Because the purpose has changed!

Separation of concerns, old school

The terms were "web pages" and "information superhighway"

Content consumption is the main activity

Separation of concerns, modern

// Remember jQuery plugins?
$('select').select2()

css-in-js today is catching up with a decade of html-in-js evolution

html-in-js@0.1.0
// SPA frameworks via template engines
var TitleView = Backbone.View.extend({
  template: _.template('<h1><%=text%></h1>')
  // Or Pug, Mustache, Handlebars, etc
})

We experimented A LOT to get to where we are today

// Modern components
function Title({ text, className, }){
  return (
    <h1 { ...{ className } }>{ text }</h1>
  )
}
html-in-js@0.2.0
html-in-js@1.0.0

So now we're running experiments, resulting in css-in-js fatigue

But that's a good GREAT thing!

Since we acknowledge CSS's shortcomings in the modern web

And smart people are coming up with new approaches to tackle these problems all the time!

Smart people experimenting

The premise of css-in-js isn't just catching up to modern front-end

  • Global namespace, lack of isolation and non deterministic resolution (specificity) are replaced with:​
    • Scoped styles - not a best practice (which must be enforced), but the default
  • Lack of dependency management and interoperability:
    • ​It's just JS! Use standard tooling: modules and npm
  • Sharing values with JS:
    • Built-in, by definition :)

The real premise of css-in-js is reimagining what's possible compared to plain old CSS!

  • Dead code elimination, ​Code coverage and Testing
  • Critical CSS: above the fold inlining
  • ​Smarter optimizations:
    • html-in-js + css-in-js = super minification with name mangling, like we've had in JavaScript for years
    • Stuff like webpack's CommonsChunkPlugin

Optimizations and

static analysis

Standardization, modularity and code reuse

  • Standard libraries instead of current CSS silos: think stuff from Sass land and lodash for CSS
  • CSS component packages

BUT WAIT, THERE'S MOAR!!1111

Ready for the next best thing since sliced bread?

Non browser styling

Some popular css-in-js libraries

JSS

Styletron

styled-components

We 💜 React because it's component model and API make an excellent DOM abstraction

Guess what? The same applies to CSS abstractions!

fela

Aphrodite

CSS modules

CSS Modules (most common today)

/* toast.css */
.message { font-weight: bold; }
.success { color: green; }
.fail    { color: red; }
import {
  message, success, fail,
} from './toast.css'

function Toast({ isSuccess, text }){
  const statusClass = isSuccess ? success : fail
  const className = `${message} ${statusClass}`
  return <div { ...{ className } }>{text}</div>
}
// usage
<Toast { ...{isSuccess: true, text: 'WIN' } } />
<Toast { ...{isSuccess: false, text: 'FAIL'} } />

CSS Modules (output)

/* compiled css */
.toast_message_116zl { font-weight: bold; }
.toast_success_116zl { color: green; }
.toast_fail_116zl { color: red; }
/* file_class_random { rule } */
<!-- rendered HTML -->
<div class='
  toast_message_116zl
  toast_success_116zl'>WIN</div>

<div class='
  toast_message_116zl
  toast_fail_116zl'>FAIL</div>
import './toast.css'

function Toast({ isSuccess, text }){
  const statusClass = isSuccess ?
    'success' : 'fail'
  const styleName = `${message} ${statusClass}`
  }
  return <div { ...{ styleName } }>{text}</div>
}
  • Does everything for you, adds <= 10% + 2kb
  • No need for camelCase names
  • Clear distinction between global CSS and CSS modules via props: className='global-css' styleName='local-module'
  • I swear, I found this image online!
  • Angular (or whatever it's called now) has a built-in implementation of CSS modules - enabled by default. Jokes aside, this is awesome!
  • The documentation for it, unlike that image, I wasn't able to find and got help from friends who use Angular.
  • 角度文件, if you can read that (that's Google translate of "angular documentation")

JSS

import jss from 'jss'
import preset from 'jss-preset-default'
import color from 'color'

// One time setup with plugins and settings
jss.setup(preset())

// Create or compose your CSS
const styles = {
  button: {
    fontSize: 12,
    '&:hover': { // CSS pseudo-classes
      background: 'blue'
    }
  },
  ctaButton: {
    extend: 'button', // mixins
    '&:hover': {
      background: // Runtime JS colors
        color('blue').darken(0.3).hex()
    }
  },
  '@media (min-width: 1024px)': {
    // CSS media queries
    button: {
      width: 200
    }
  }
}

// Attach stylesheet to DOM (or SSR)
const {classes} =
  jss.createStyleSheet(styles).attach()

// Render your HTML with generated classes
document.body.innerHTML = `
  <button class="${classes.button}">
    Button
  </button>
  <button class="${classes.ctaButton}">
    CTA Button
  </button>
`
  • Fast JS to CSS compiler in client and server-side
  • Non opinionated, compatible with many libraries
  • Small and extensible via plugins (true for all css-in-js

react-jss is JSS on steroids

import React from 'react'
import injectSheet from 'react-jss'

const styles = {
  button: {
    background(props){ return props.color }
  }
}

function Button({ classes, children }){
  const buttonProps = {
    className: classes.button,
    children
  }
  return <button { ...buttonProps } />
}

export default injectSheet(styles)(Button)
  • Lazy: stylesheet created/removed on mount/unmount
  • Styles and CSSOM are kept minimal due to laziness
  • A single sheet is shared between all elements
  • React Hot Loader FTW!

react-jss + decorators

import React, { Component } from 'react'
import injectSheet from 'react-jss'

const styles = {
  button: {
    background({ color }){ return color }
  }
}

@injectSheet(styles)
export default class Button extends Component {
  render(){
    const { classes, children } = this.props
    const buttonProps = {
      className: classes.button,
      children
    }
    return <button { ...buttonProps } />
  }
}
import {h1} from 'styled-components'

const Title = h1`
  font-family: Comic Sans MS;
  color: blue;
`
// Usage
<Title>Hello World!</Title>

Why even bother with class names?

They are merely an implementation detail

styled-components + JSS = styled-jss

import styled from 'styled-jss'

const Title = styled('h1')({
  fontFamily: 'Comic Sans MS',
  color(props){ return props.theme.textColor }
})
// Composition
const ScaryTitle = styled(Title)({
  color: 'red'
})
// Usage
<Title>OHAI!</Title>
<ScaryTitle>FAIL</ScaryTitle>
// a styled component
import { StyleSheet, css } from 'aphrodite'

const styles = StyleSheet.create({
  title: { color: 'green' }
})

function Heading(props){
  const headingProps = {
    ...props,
    className: css(styles.title)
  }
  return <h1 { ...headingProps } />
)

// SSR with critical CSS inlined
import { StyleSheetServer } from 'aphrodite'

const { html, css } = StyleSheetServer.renderStatic(
  () => ReactDOMServer.renderToString(<App/>)
)

// Send in response body
const criticalCss = `
  <style data-aphrodite>
    ${css.content}
  </style>
`
import { styled } from 'styletron-react'

const Title = styled('h1', {
  fontFamily: 'Comic Sans MS',
  color(props){ return props.theme.textColor }
})

// Composition
const ScaryTitle = styled(Title)({
  color: 'red'
})

// Usage
<Title>OHAI!</Title>
<ScaryTitle>FAIL</ScaryTitle>

Looks familiar, doesn't it?

styletron-react, client

import Styletron from 'styletron-client'
import {StyletronProvider} from 'styletron-react'

// SSR rehydration
const styleElements = document
  .getElementsByClassName('_styletron_hydrate_')

ReactDOM.render(
  <StyletronProvider
    styletron={ new Styletron(styleElements) }
    >
    <App/>
  </StyletronProvider>,
  document.getElementById('app')
)
import Styletron from 'styletron-server'
import {StyletronProvider} from 'styletron-react'

export default function render(){
  const styletron = new Styletron()
  const appMarkup = ReactDOM.renderToString(
    <StyletronProvider { ...{ styletron } }>
      <App/>
    </StyletronProvider>
  )
  const stylesForHead = styletron
    .getStylesheetsHtml()
  return `<html><head>${ stylesForHead }</head>
    <body>${ appMarkup }</body></html>`
}

Not perfect, yet

  • Debugging and developer experience are lackluster
    • The same is just as true for all CSS preprocessors, so not really worse when compared
    • In React things are better, since it has awesome devtools 🎉
  • Concept is still considered novel, experimental and controversial by many (which is why I'm here :)
  • Client-side css-in-js runtime incurs an overhead:
    • Initial download and bootstrapping
    • Subsequent processing (hopefully mitigated by uber-optimizations)

OK, so what's my point?

Good news

.

Bad news

The good news is,

With so many choices, each with a variety features and tradeoffs

I have no idea which ones will become popular ¯\_(ツ)_/¯

IT'S A CELEBRATION!

It's as if we've seen this before ;)

You should try these for yourself and draw your own conclusions!

The bad news is,

CSS is here to stay

And you still have to learn it

Sources and references

Thank you and

stay curious :)

react { css-in-js: overflow }

By Nick Ribal

react { css-in-js: overflow }

While JS and HTML have progressed incredibly in recent years, CSS is the last bastion of old-school web development which we all love to hate. CSS-in-JS is the latest attempt at solving this hard problem. If it succeeds, CSS-in-JS will fast-forward CSS into the modern era. Moreover, it will revolutionize how we compose, maintain, share and optimize styling across the modern web, native apps and collaborate with designers and product people. I will present an overview of the history, evolution, challenges, current solutions and benefits in this exciting landscape!

  • 2,277