Giter Club home page Giter Club logo

css-in-js-101's Introduction

Stand With Ukraine

CSS-in-JS 101 Twitter Follow

CSS-in-JS 101

What is CSS-in-JS?

CSS-in-JS is an umbrella term for technologies which help you define styles in JS in more component-approach-way. The idea was introduced by @vjeux in 2014. Initially, it was described as inline styles, but since then battlefield changed a lot. There are about 50 different solutions in this area.

References

Inline styles

This is built in feature of React. You can pass styles as an object to the component and it will be converted to the string and attached as style attribute to the element.

Pros

  • No global namespace
  • Full isolation
  • No non-deterministic resolution
  • Clear dependencies
  • Dead code elimination
  • Variables, Passing variable from JS to CSS

Cons

  • Code duplication in case of SSR.
  • Additional costs in JS payload. Remember that styles which are embedded in JS are not for free. It is not only about download time, it is also about parsing and compiling. See this detailed explanation by Addy Osmani, why JS is expensive
  • No media queries (@media)
  • No CSS animations (@keyframes)
  • No pseudo classes (:hover)
  • No web fonts (@font)
  • No autoprefixer (well there is inline-style-prefixer)

Example

TODO: verify

JSX:

hundred_length_array
  .map(x => <div key={x} style={{color: "#000"}}></div>)

Generated HTML:

<div style="color:#000"></div>
...(98 times)
<div style="color:#000"></div>

Inline styles vs CSS-in-JS

@mxstbr differentiate Inline styles and CSS-in-JS. By Inline styles he means React built-in support for style attribute and by CSS-in-JS he means a solution which generates CSS and injects it via style tag.

On the other hand, CSS-in-JS is the term coined by @vjeux in 2014 and he meant Inline styles. Inline styles is not React-only feature. There is, for example, Radium which also uses inline styles.

So I would suggest to use CSS-in-JS as an umbrella term and specify implementation:

  • inline styles
  • style tag. Also can be referred as "style element" or "real CSS"
  • mixed (like Radium)

References

Style tag

This approach is alternative to Inline styles. Instead of attaching styles as property to the element you are inserting real CSS in style tag and append style tag to the document.

Pros and cons can vary from implementation to implementation. But basically, it looks like this:

Pros

  • (Almost) No global namespace
  • (Almost) Full isolation
  • (Almost) No non-deterministic resolution
  • Clear dependencies
  • Dead code elimination
  • Variables (depends on implementation)
  • No code duplication in case of SSR
  • Additional costs in JS payload (depends on implementation)
  • Media queries (@media)
  • CSS animations (@keyframes)
  • Pseudo-classes (:hover)
  • Web fonts (@font)

Cons

Cons depend on implementation.

Example

from this blog post:

const MyStyledComponent = props =>
  <div className="styled">
    Hover for red
    <style dangerouslySetInnerHTML={{__html: `
      .styled { color: blue }
    `}} />
  </div>

Generated HTML:

<div class="styled">
  Hover for red
  <style>      .styled { color: blue }    </style>
</div>

CSS Modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. All URLs (url(...)) and @imports are in module request format (./xxx and ../xxx means relative, xxx and xxx/yyy means in modules folder, i. e. in node_modules).

Pros

  • (Almost) No global namespace
  • (Almost) Full isolation
  • (Almost) No non-deterministic resolution
  • Clear dependencies
  • (Almost) Dead code elimination
  • Variables, Sharing variables in CSS and exposing it to JS
  • No Code duplication in case of SSR
  • No Additional costs in JS payload.
  • Media queries (@media)
  • CSS animations (@keyframes)
  • Pseudo-classes (:hover)
  • Web fonts (@font)
  • Autoprefixer

Cons

See all points with "(Almost)"

Dead Code Elimination, Critical CSS

Strictly speaking, there are no official solutions to those problems in CSS Modules, but there is some work in this direction. Correct me if I'm wrong if there is one, why isn't it promoted?

References

Global Name Space, Globally Unique Identifier

All declarations in CSS are global, which is bad because you never know what part of application change in global scope will affect.

Possible solutions

  • Attach styles to each element (Inline styles)
  • Use Globally Unique Identifiers for classes (CSS modules)
  • Use naming conventions (BEM and others)

References

Dependencies

Ability to programmatically resolve dependency between component (JS and HTML) and styles, to decrease error of forgetting to provide appropriate styles, to decrease fear of renaming CSS classes or moving them between files.

Possible solutions

  • bundle styles with-in component (CSS-in-JS)
  • import styles from "styles.css" (CSS modules)

Related

  • Dead Code Elimination

Minification

There is more than one aspect of minification. Let's explore:

Traditional CSS minification

This is the simplest approach - remove whitespace, minify color name, remove unnecessary quotes, collapse CSS rules etc. See big list of minifiers here

Minification of class name

In CSS modules and CSS-in-JS you do not use class names directly, instead, you use JS variables, so class names can be easily mangled.

Note: This type of minification is not possible for traditional CSS.

Example

import styles from "styles.css";

<div className={styles.example} />

styles compiles to { example: "hASh"}

Dead Code Elimination

Because there is no connection between JS/HTML and CSS, you cannot be sure if it is safe to remove some parts of CSS or not. If it is stale or not? If it is used somewhere or not?

CSS-in-JS solves this problem because of a link between JS/HTML and CSS is known, so it is easy to track if this CSS rule required or not.

Related

  • Dependencies
  • Critical CSS
References

Critical CSS

The ability of a system to extract and inline styles in head required for current page viewed by the user not more nor less.

Note: this is slightly different from the definition by @addyosmani, which defines critical as above-the-fold.

Example

aphrodite:

import { StyleSheet, css } from 'aphrodite'

const styles = StyleSheet.create({
  heading: { color: 'blue' }
})

const Heading = ({ children }) => (
  <h1 className={css(styles.heading)}>
    { children }
  </h1>
)
import { StyleSheetServer } from 'aphrodite'

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

Related

  • Dependencies
  • Dead Code Elimination
  • SSR
References

Automatic Atomic CSS

In CSS modules and CSS-in-JS you do not use class names directly, instead, you use JS variables, so class names can be easily mangled. The same as in "Minification of class name". But we can go further - generate smaller classes and reuse them to achieve smaller CSS

Note: This type of minification is not possible for traditional CSS.

Example

styletron

import {injectStyle} from 'styletron-utils';
injectStyle(styletron, {
  color: 'red',
  display: 'inline-block'
});
// → 'a d'
injectStyle(styletron, {
  color: 'red',
  fontSize: '1.6em'
});
// → 'a e'

Related

References

Sharing Constants, variables in CSS

There are different approaches.

Sharing variables inside CSS

Sharing variables in CSS and exposing it to JS

This is mainly a feature of CSS modules with variables.

Example

postcss-icss-values

/* colors.css */
@value primary: #BF4040;

Sharing variables in CSS:

@value primary from './colors.css';

.panel {
  background: primary;
}

Exposing it to JS:

import { primary } from './colors.css';
// will have similar effect
console.log(primary); // -> #BF4040

Passing variable from JS to CSS

This is only possible with CSS-in-JS. This approach gives maximum flexibility and dynamics.

Example

styling

import styling from 'styling'
import {baseColor} from './theme'

export let button = styling({
  backgroundColor: baseColor
})

Related

  • Overriding theme variables

Non-deterministic Resolution

Resolution depends on the order of declarations in stylesheets (if declarations have the same specificity).

References

Isolation

Because of CSS cascading nature and Global Name Space, there is no way to isolate things. Any other part code can use more specificity or use !important to override your "local" styles and it is hard to prevent this situation

Strictly speaking, only inline styles gives full isolation. Every other solution gives just a bit more isolation over pure CSS, because of solving Global Name Space problem.

References

Two CSS properties walk into a bar.

A barstool in a completely different bar falls over.

— Thomas "Kick Nazis out, @jack" Fuchs (@thomasfuchs) July 28, 2014

Theming

The idea is to be able to change the look of existing components without the need to change actual code.

Overriding styles

This way you can override styles based on "class names" (keys of objects in case of inline styles).

Example

react-themeable

With CSS modules:

import theme from './MyComponentTheme.css';
<MyComponent theme={theme} />

Same with inline styles:

const theme = {
  foo: {
    'color': 'red'
  },
  bar: {
    'color': 'blue'
  }
};
<MyComponent theme={theme} />

Overriding theme variables

This way you can override styles based on variables passed to the theme. The theme basically works like a function - accepts variables as input and produce styles as a result.

Related

  • Variables, Passing variable from JS to CSS

SSR, Server-Side Rendering

HTML SSR

Make sure that CSS-in-JS solution doesn't brake default React isomorphism e.g. you are able to generate HTML on the server, but not necessary CSS.

CSS SSR

Be able to prerender CSS on the server the same way as HTML can be prerendered for React.

Example

aphrodite:

import { StyleSheet, css } from 'aphrodite'

const styles = StyleSheet.create({
  heading: { color: 'blue' }
})

const Heading = ({ children }) => (
  <h1 className={css(styles.heading)}>
    { children }
  </h1>
)
import { StyleSheetServer } from 'aphrodite'

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

TODO: add example with Inline Styles

Related

  • Critical CSS

Zero runtime dependency

Almost all CSS-in-JS solutions have runtime dependency e.g. library required to generate styles at runtime and CSS encoded as JS.

Some solutions do not have this issue, they basically vanished after build step. Examples: CSS modules, linaria.

Example

linaria

import React from 'react';
import { css, styles } from 'linaria';

const title = css`
  text-transform: uppercase;
`;

export function App() {
  return <Header {...styles(title)} />;
}

Transpiled to:

.title__jt5ry4 {
  text-transform: uppercase;
}
import React from 'react';
import { styles } from 'linaria/build/index.runtime';

const title = 'title__jt5ry4';

export function App() {
  return <Header {...styles(title)} />;
}

CSS-in-JS implementation specific features

Non-DOM targets

React can target different platforms, not just DOM. It would be nice to have CSS-in-JS solution which supports different platforms too. For example: React Native, Sketch.

CSS as object (object literal)

const color = "red"
const style = {
  color: 'red',
}

CSS as template literal

const color = "red"
const style = `
  color: ${color};
`

Framework agnostic

Does it depend on React or not?

Build step

If build step required or not?

Related

  • SSR
  • Progressive enhancement
  • Dynamic

Dynamic

If you can pass values to CSS at runtime.

Note: it is not the same as Variables, Passing variable from JS to CSS, for example in linaria you can pass variables from JS to CSS, but only at build time.

Note 2: cannot stop myself from drawing analogy between static and dynamic type systems.

Related

  • Build step
  • Variables, Passing variable from JS to CSS

Generate components based on CSS

If your component has pretty simple structure and you care more about how it looks instead of markup (which most likely will be div anyway). You can go straight to write CSS and library will generate components for you.

Examples

decss

import React from 'react'
import { Button } from './style.css'

<Button>
  Panic
</Button>

styled-components

import React from 'react'
import styled from 'styled'

const Button = styled.button`
  background-color: red;
`;

<Button>
  Panic
</Button>

Developer tools integration

If there are special perks for developer tools?

Example

emotion supports source maps for styles authored in javascript

GIF of source maps in action

Progressive enhancement, graceful degradation

If you do not know what is it read this article.

In the context of CSS-in-JS it boils down to one question - will your website be styled with disabled JS.

The first requirement would be to have some HTML rendered on the server (SSR or snapshoting). After this you have two options:

  • prebuild CSS e.g. Build step required
  • rendered CSS e.g. CSS SSR required

Related

  • SSR
  • Build step

Uncovered subjects

Security

See this post

Async components

Also known as code splitting, dynamic import

Async component is a technique (typically implemented as a higher order component) for loading components with dynamic import. There are a lot of solutions in this field here are some examples:

References

Webpack has a feature to split your codebase into “chunks” which are loaded on demand. Some other bundlers call them “layers”, “rollups”, or “fragments”. This feature is called “code splitting”.

Code splitting

CSS-in-JS and Async components

This works for most CSS-in-JS solutions because CSS is bundled inside JS. This is a more complicated task for CSS modules. See: Guide To JavaScript Async Components.

Atomic CSS

Also known as immutable, functional, utility-class.

Idea boils down to use one property per class, so you create required look by composing more than one class. Because each class contains only one property, you do not override those properties and this can be interpreted as immutability.

Do not confuse with Atomic CSS framework.

References

Animations

Sequential

Basically CSS3 animations. Pros: can be GPU accelerated.

Interruptible

Also known as interactive.

Basically JS animations. Pros: can be interrupted.

References


css-in-js-101's People

Contributors

stereobooster avatar thiamsantos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

css-in-js-101's Issues

Variable scope

First of all, nice work, it was a great read!

I discuss some of the issues touched in this repository in my library freestyler. Interesting discussion to explore could be the variable scope CSS template has access to. For example, I split those libraries in 1-5th generation.

RFC: A better way to describe frontend setup (HTML, JS, CSS)

Problem

I try to understand all tradeoffs of different CSS-in-JS approaches. One of the problems is to understand what the setup assumed by the solution and how it affects performance. By "setup" I mean - do we use SSR or Snapshots, do we serve CSS as separate files, do we have critical CSS, do we have async components (dynamic import), etc.

Representation matters

To solve the problem, I want to introduce "language" (symbolic representation) which will be able to describe all cases in a dense form. Representation is important, take a look this talk by Bret Victor to understand why.

Alphabet

We have three major "axis" of a web application, they are HTML, CSS, JS. They will be represented by squares with a letter inside:

  • |J| - JavaScript
  • |H| - HTML e.g. Server-Side Rendering or Snapshot
  • |C| - CSS

On the diagram, they always go in the same order: |J|H|C.

If "axis" is missing it is represented by an empty box. For example, c-r-a doesn't have SSR out of the box, so there is no prerendered HTML: |J| |C|

--- - "browser" line, everything which is above the line is content loaded in the browser. Everything below the line is not loaded content, for example async components. Example react-loadable (or any other async library):

|J| <-- React app
---
|J| <-- async components

~~~ - "browser" line, but loading happens in none-blocking manner. For example loadCSS :

|C|
~~~

Important note: all content of that type should be loaded in the asynchronous mode to be considered as async. So if you have two script tags (with src) in HTML, both should be async.

One box of the type represents 100% of the required content for the current page to work. If there are two boxes of the same type (above the line), it means there is dead code, which is not needed for the current page. For example, a dead code in CSS in a big library, like bootstrap:

|C|
|C|
---

CSS-in-JS in this dieagram will be represented by CSS square (|C|) in JS "axis".

|C|
|J|
---

Note: linaria is CSS-in-JS, but will be drawn other way.

Critical CSS inlined in HTML wil be represented by CSS square (|C|) in HTML "axis".

|C|
|H|
---

Strictly speaking, every React application carries HTML inside, so it should be drawn as HTML square (|H|) in JS axis, but this will create more noise and confusion, so I prefer to omit it.

So now you know the alphabet.

Practical example

Let's analyze one setup, so you catch the idea.

Assume we have c-r-a project with bootstrap, like this one. Let's draw it:

    |C| <-- dead code from bootstrap
|J| |C|
-------
   ^------- no prerendered HTML

What does this tell us? It has pretty poor load performance, there is no HTML, so nothing will be loaded till JS execution. Slow first time to paint. Also slow TTI - everything loaded synchronously.

Let's use react-snap - a small tool for prerendering SPAs, very useful for small mostly static websites.

    |C|
|J|H|C|
-------

This will improve time to first paint. But still we have huge CSS (with dead code) loaded synchronously, let's enable inlineCss option, which will inline critical CSS and make CSS load async:

  |C|C|
|J|H|C|
-----~~

Ah much better - first paint now much faster. HTML and critical CSS are delivered with the first request, so the browser will start to render ASAP. Nice but TTI still can be a problem, there is a JS which is loaded in a synchronous manner. On the other side given website is pretty usable without JS - content is present via prerendered HTML. Filters will not work without JS, but this is not a big issue. So we can use asyncScriptTags: true

  |C|C|
|J|H|C|
~~---~~

Now website will be way faster to load.

Weak sides

You need to be aware that this setup does not represent all aspects of a web application. For example:

  • it does not show capabilities of CSS-in-JS (see @MicheleBertoli list for this)
  • it doesn't communicate developer experience. It feels so nice to work with styled-components
  • and probably it misses a lot of different stuff which I forgot to mention

Did I miss something? This diagram doesn't show web fonts for example, which is known performance bottleneck. Comments are welcome.

Related: https://mntr.dk/2014/getting-started-with-assetgraph/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.