CSS-in-JS 101
- What is CSS-in-JS?
- Inline styles
- Style tag
- CSS Modules
- Global Name Space, Globally Unique Identifier
- Dependencies
- Minification
- Sharing Constants, variables in CSS
- Non-deterministic Resolution
- Isolation
- Theming
- SSR, Server-Side Rendering
- Zero runtime dependency
- CSS-in-JS implementation specific features
- Progressive enhancement, graceful degradation
- Uncovered subjects
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
- @vjeux, 2014
- Big list of CSS-in-JS solutions
- Comparison of CSS-in-JS by styled-components
- Yet another comparison
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
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?
- Comment by @sokra on critical CSS
- isomorphic-style-loader
- Support Tree shaking of CSS
- Atomic CSS a la styletron
- CSSO scopes
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
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
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
- CSS variables
- Can I use css variables
- CSS variables in preprocessors, like PostCSS, SASS etc.
Sharing variables in CSS and exposing it to JS
This is mainly a feature of CSS modules
with variables.
Example
/* 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
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.
β Thomas "Kick Nazis out, @jack" Fuchs (@thomasfuchs) July 28, 2014
A barstool in a completely different bar falls over.
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
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
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
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
import React from 'react'
import { Button } from './style.css'
<Button>
Panic
</Button>
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
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
- Dynamic import is the TC39 proposal.
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.