Disclaimer

 

There are a lot of strong opinions around CSS in JS. These are just my views.


It's also a huge topic, that I'll only touch on.
 

Issues with CSS

  • Global namespace
  • Repetitive code, less expressive
  • Dead code elimination
  • Order of loading / Cascading
  • Animations
  • Selectors

Problems with the global namespace

Let's make a nav bar

.nav {
  padding: 0;
  text-align: center;
  list-style-type: none;
  background-color: #483531;
  color: white;
  height: 60px;
}
.nav li {
  display: inline-block;
  padding-left: 20px;
  padding-right: 20px;
  line-height: 60px;
}

Someone else developing in isolution creates a page sub navigation and unknowingly gives it the same class name

.nav {
  background-color: #143d5d;
  color: #d0dded;
  display: inline-block;
  padding-right: 20px;
}

.nav li {
  margin: 40px 0px;
}

After both changes are committed the expected result is broken

Expected Result

Actual Result

CSS is not very expressive

You can't do:

shapes.forEach(shape => {
  .$shape.name {
    width: $shape.width;
    height: $shape.height;
  }
})
.myClass {
  position: absolute;
  left: Math.random() * 400;
}

Loops

Non simple math

.content {
  if ($backgroundColor == light)
    color: black;
  else ($backgroundColor == dark)
    color: white;
}

If / Else

.overlay {
  opacity: calculateTransparency($animationStep);
}

Custom functions

You can now do

element {
  --main-bg-color: brown;
} 

element {
  background-color: var(--main-bg-color);
}

Variables Custom Properties

Dead code elimination

It's a problem with all code, but it's easily done with CSS

 

Remove some html and often the css it uses isn't removed

Why? There's no easy way of knowing if those classes are used somewhere else. Let alone in html but also there may be JavaScript references

 

 

 

 

const box = document.querySelector('.largeBox')

const bounds = box.getBoundingClientRect()

It's safer to never delete CSS code.

So it often grows and grows and never shrinks
 

Order of loading / Cascading

Can't drop style sheets. The order they are defined affects the presentation​

 

Makes it hard to asynchronously load styles

Can mean a user's navigation behavior will impact how pages are rendered

 

Can create hard to reproduce bugs

A really contrived example:

/home

/loading

/home

Styles from the loading page override the home page styling and it looks horrible

/loading

/home

/loading

Can create hard to reproduce bugs

Loading page looks completely different

User instead decides to refresh the browser here

Animations

CSS animations are flawed, they're not interruptible

 

You have to wait for them to finish

 

Not useful when user does things quickly or clicks something in the middle of a transition


95% of the time you want to animate UI components


Fire and forget
 

Selectors

Breaks component encapsulation

 

Easy to trip up

 

Can mean nested components end up with styles from parent components


 

Other solutions

Avoid non specific selectors if possible

With React, give everything an explicit class name, don't rely on selectors

.container {
  width: 400px; 
}

.container ul {
  padding-left: 0;
}

.container ul li {
  display: inline-block;
}
.container {
  width: 400px; 
}

.list {
  padding-left: 0;
}

.listItem {
  display: inline-block;
}

Instead of

Do this

Preprocessors

LESS, SASS, SCSS

 

Solves some of the issues with CSS, gives you:

• Variables​​

• Scope

Problems:

• Is it even needed? Now we have native variables

• ​​Have to learn a new language

Use inline styles everywhere

 

Was popular around 2015/16

Actually solves loads of CSS problems

Was the predecessor to CSS in JS

 

Problems:

• Hard to do pseudo styles and media queries

• Cascading not an option for times that's useful

• Overriding styles becomes hard

CSS Modules

Local scoped class names

Solves global name space class name collisions

Can still write native CSS

CSS in JS

How it works

Write CSS in JavaScript

var navStyles = {
  padding: 0,
  textAlign: 'center',
  listStyleType: 'none',
  backgroundColor: '#483531',
  color: 'white',
  height: 60,
}
var navItem = {
  display: 'inline-block',
  paddingLeft: 20,
  paddingRight: 20,
  lineHeight: 60,
}

Delimiter case -> Camel case

Can drop the 'px'

Values becomes strings or numbers

Example:

A few different implementations

react-look

Seemed a great solution, solved a lot of the problems but then creator stopped maintaining it in favor of building fela.

Same syntax as react-native

const styles = StyleSheet.create({
  header: {
    transition: '200ms all linear',
    '@media (min-height: 800px)': {
      fontSize: 13,
      ':hover': {
        fontSize: 15
      }
    },
  },
  title: {
    fontWeight: 800,
    // use functions to inject props, 
    // state or context values
    fontSize: (props, state, context) => 
        props.size * state.zoom
  }
})

export default look(Header)
class Header extends Component {

  render() {
    return (
      // Styles are basically applied 
      // using the `className` property
      <header className={styles.header}>
        <h1 className={styles.title}>
          {this.props.title}
        </h1>
      </header>
    )
  }
}

Example code

styled-components

 

Advantages

• Is very popular
• Easy barrier to entry

(if you're new to CSS in JS, can still write styles in CSS syntax)

 

 

 

// Create a <Title> react component that 
// renders an <h1> which is centered, 
// palevioletred and sized at 1.5em
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// Create a <Wrapper> react component 
// that renders a <section> with some 
// padding and a papayawhip background
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

Disadvantage

• Less flexible than writing styles as JS objects

​• Less flexibility by blurring styles and classes

// Use them like any other React component 
// – except they're styled!
<Wrapper>
  <Title>
    Hello World, this is my first 
    styled component!
  </Title>
</Wrapper>

Example

fela

Haven't used it in a real world project yet but author has a proven track record with react-look, rendering looks similar to styled-components.

Advantages

• Very powerful and flexible

• Functional, complements JS well

• Easily pass in props for dynamic styles

Disadvantages

• More verbose
• High barrier to entry

const rule = state => ({
  textAlign: 'center',
  padding: '5px 10px',
  background: state.primary ? 
    'green' : 'blue',
  fontSize: '18pt',
  borderRadius: 5,
  ':hover': {
    background: state.primary ? 
        'chartreuse' : 'dodgerblue',
    boxShadow: '0 0 2px rgb(70, 70, 70)'
  }
})
import { 
  createComponent, 
  Provider 
} from 'react-fela'

const Button = createComponent(rule, 'button')

// Render as JSX
<Provider renderer={renderer}>
  <Button primary>Primary</Button>
  <Button>Default</Button>
</Provider>

Example

jss and react-jss

My package of choice.

Solves most issues

const styles = {
  button: {
    background: props => props.color
  },
  label: {
    fontWeight: 'bold'
  }
}
const Button = ({classes, children}) => (
  <button className={classes.button}>
    <span className={classes.label}>
      {children}
    </span>
  </button>
)

export default injectSheet(styles)(Button)

Example

How does it solve the original CSS issues?

Global class name collisions

const wrapperStyles = {
  wrapper: {
    position: 'relative',
    marginBottom: 120,
  },
  header: {
    display: 'inline-block',
    background: '#ebebeb',
  },
}

const loginStyles = {
  error: {
    color: 'red'
  },
}

Example

Like CSS Modules, all styles are automatically locally scoped

When rendered as CSS on the page name collisions are automatically avoided

In development

In production

Output on the page

It's very expressive

It's just JavaScript, so its extremely expressive.

const styles = {
  button: {
    background: props => props.color
  },
}

Can make css choices using component props

const styles = {
  label: (props) => {
    
    var color

    switch (props.animalType){
      case 'dog': color = 'brown'; break;
      case 'lion': color = 'yellow'; break;
      default: color: 'blue'
    }
    
    return {
      display: 'block',
      color,
    }
  }
}

Can use functions, with dynamic logic to figure out styles

const styles = {}, 
  colors = ['red', 'blue', 'yellow'], 
  classes = ['header', 'footer', 'content']

classes.forEach((c, index) => {
  styles[c] = { 
    backgroundColor: colors[index],
  }
}) 

Could build classes from data structures

Dead code elimination

Styles are only used by their component

So removing a component removes all CSS which is now dead code

Much easier to manage
 

Order of loading / cascading

Stylesheets are automatically mounted and dismounted

Hooks into React's component mount and unmount life cycle

Only stylesheets needed for the current view are in the DOM

When the user navigates, unused stylesheets are dismounted

Solving animations

react-motion is an incredible tool

 

uses springs - no duration required

Just provide the start and end states and the in between states are automatically calculated.

You can use props and state to trigger it to animate to a new state.


Interruption automatically handled

 

Usage

 

import {Motion, spring} from 'react-motion';
// In your render...
<Motion defaultStyle={{x: 0}} style={{x: spring(10)}}>
  {value => <div>{value.x}</div>}
</Motion>

Selectors

As you're only styling a single component its very easy to give each aspect of the component its own class name, therefore selectors usually aren't needed

 

 

 

 

End

 

https://slides.com/allytaft

CSS in JS

By Ally Taft

CSS in JS

Highlighting and solving some of the problems of CSS by using JavaScript

  • 258