Editor’s note: This post was last updated on 29 July 2021 for accuracy and clarity. For more up-to-date information on CSS-in-JS, you can also check out this article on CSS-in-JS libraries.
What does CSS-in-JS solve?
In addition to traditional CSS, inline styles and CSS-in-JS can also be used to style React applications.
Even though inline styles enable you to pass a JavaScript object to the style attribute (as seen in the code snippet below), it doesn’t support all CSS features:
import React from "react"; function App() { const myStyle = { fontSize: 24, lineHeight: "1.3em", fontWeight: "bold" }; return ( <div> <p style={myStyle}>Hello world</p> </div> ); } export default App;
On the other hand, CSS-in-JS libraries like Aphrodite, styled-components, JSS, Emotion, Radium, etc. give developers the ability to not only style components with JavaScript but also tackle some CSS limitations, such as the lack of dynamic functionality, scoping, and portability when using them:
// Here's an implementation of the inline style code snippet above using Aphrodite import React from "react"; import { StyleSheet, css } from "aphrodite"; function App() { const styles = StyleSheet.create({ myStyle: { fontSize: 24, lineHeight: "1.3em", fontWeight: "bold" } }); return ( <div> <span className={css(styles.myStyle)}>Hello World!</span> </div> ); } export default App;
In this article, I will highlight five things you didn’t know you could do in CSS-in-JS using the CSS-in-JS libraries mentioned above as a case study. 👇
1. CSS-in-JS lets you refer to other styled components
Libraries like styled-components and Emotion allow you to use tagged template literals to create React components from styles:
// Create a component that renders a <p> element with blue text import React from "react"; import styled from "styled-components"; function App() { const BlueText = styled.p` color: blue; `; return ( <div> <BlueText>My blue text</BlueText> </div> ); } export default App;
But they also allow you to target other styled components (like if you were using CSS selectors):
import React from "react"; import styled from "styled-components"; function App() { const ImportantText = styled.div` font-weight: bold; `; const Text = styled.div` color: gray; ${ImportantText} { font-style: italic; } `; return ( <div> <Text> Text in gray <ImportantText>Important text in gray, bold and italic</ImportantText> </Text> <ImportantText>Important text bold</ImportantText> </div> ); } export default App;
This is useful when it is combined with pseudo-classes; for example, to change the color of a component on hover:
import React from "react"; import styled from "styled-components"; function App() { const ImportantText = styled.div` font-weight: bold; `; const Text = styled.div` color: gray; &:hover ${ImportantText} { color: red; } `; return ( <div> <Text> Text in gray <ImportantText>Important text in gray, bold and italic</ImportantText> </Text> <ImportantText>Important text bold</ImportantText> </div> ); } export default App;
2. JSS (or other libraries) can extend the features of some libraries
Let’s say you’ve used Aphrodite to style your application and now you need to support themes.
The problem is that Aphrodite doesn’t support theming easily. At least, not as easy as Emotion does.
However, there are two projects that bridge the core of JSS with Aphrodite and styled-components: aphrodite-jss and styled-jss.
This way, you can keep the good parts of Aphrodite (or styled-components) and use all the features and plugins of JSS, from rule caching to rule isolation, and for themes, the theming package, which provides the following high-order components:
ThemeProvider
, which passes a theme object down the React tree by contextwithTheme
, which allows you to receive a theme object and it updates as a property
For example:
import React from "react"; import { createUseStyles, ThemeProvider, useTheme } from "react-jss"; function App() { const useStyles = createUseStyles({ wrapper: { padding: 40, background: ({ theme }) => theme.background, textAlign: "left" }, title: { font: { size: 40, weight: 900 }, color: ({ theme }) => theme.color }, link: { color: ({ theme }) => theme.color, "&:hover": { opacity: 0.5 } } }); const Comp = () => { const theme = useTheme(); const classes = useStyles({ theme }); return ( <div className={classes.wrapper}> <h1 className={classes.title}>Hello There!</h1> </div> ); }; const theme = { background: "blue", color: "white" }; return ( <div> <ThemeProvider theme={theme}> <Comp /> </ThemeProvider> </div> ); } export default App;
In the particular case of Aphrodite and themes, as another example, you can also use react-with-styles, which interfaces with Aphrodite and JSS, among others, to access theme information when defining styles.
3. CSS-in-JS can chain multiple animations with keyframes
Unlike inline styles, CSS-in-JS allows you to define animations using keyframes.
For example, this is how it’s done with styled-components:
import React from "react"; import styled, { keyframes } from "styled-components"; function App() { const MoveAnimation = keyframes` 0% { transform: translate(0, 0); } 50% { transform: translate(50px, 0); } `; const MyComponent = styled.div` display: inline-block; margin: 50px; width: 200; position: relative; animation: ${MoveAnimation} 1.5s ease infinite; `; return ( <div> <MyComponent>Hello There!</MyComponent> </div> ); } export default App;
But what not many people know is that you can chain multiple animations by using more than one keyframe object in the animation
property.
Here’s the above example modified to combine two animations:
import React from "react"; import styled, { css, keyframes } from "styled-components"; function App() { const MoveAnimation = keyframes` 0% { transform: translate(0, 0); } 50% { transform: translate(50px, 0); } `; const ColorAnimation = keyframes` from {color: red;} to {color: blue;} `; const MyComponent = styled.div` display: inline-block; margin: 50px; width: 200; position: relative; animation: ${(props) => css` ${MoveAnimation} 1.5s ease infinite, ${ColorAnimation} 1.5s linear infinite `}; `; return ( <div> <MyComponent>Hello There!</MyComponent> </div> ); } export default App;
4. You can declare global styles with CSS-in-JS
Everything in CSS is global, and one of the purposes of using CSS-in-JS is to eliminate global style definitions.
However, there may be valid uses of global styles; for example, when you want to apply the same font styles to every element in your page.
Of course, you can always use traditional CSS, importing it via Webpack or declaring it in the index.html
file.
But if you’re serious about using JavaScript for all your styles, some libraries actually allow you to define global styles via helper components or extensions/plugins.
In Radium, you can use the Style component to render a styled element with global styles.
For example:
<Style rules={{ body: { fontFamily: 'Arial, Helvetica, sans-serif' } }} />
Will return:
<style> body { font-family: 'Arial, Helvetica, sans-serif'; } </style>
JSS uses a plugin to write global styles:
const styles = { '@global': { body: { fontFamily: 'Arial, Helvetica, sans-serif' } } }
Which will return:
body { font-family: 'Arial, Helvetica, sans-serif'; }
And in Aphrodite, you can use a third-party extension to create styles. For example:
import {injectGlobalStyles} from "aphrodite-globals"; injectGlobalStyles({ "body": { fontFamily: 'Arial, Helvetica, sans-serif', } });
This will return:
body { font-family: 'Arial, Helvetica, sans-serif'; }
5. Some CSS-in-JS libraries can test components with styles in unit tests
Some libraries contain utilities for testing components with styles.
Aphrodite provides an undocumented (at least at the time of writing this) object, StyleSheetTestUtils, which is only available for non-production environments (process.env.NODE_ENV !== 'production'
) and has three methods:
suppressStyleInjection
, which prevent styles from being injected into the DOM, and it’s useful when you want to test the output of Aphrodite components when you have no DOMclearBufferAndResumeStyleInjection
, which does the opposite ofsuppressStyleInjection
and should be paired with itgetBufferedStyles
, which returns a string of buffered styles that have not been flushed
Here’s an example of how they are used:
import { StyleSheetTestUtils, css } from 'aphrodite'; //... beforeEach(() => { StyleSheetTestUtils.suppressStyleInjection(); }); afterEach(() => { StyleSheetTestUtils.clearBufferAndResumeStyleInjection(); }); test('my test', () => { const sheet = StyleSheet.create({ background: { backgroundColor: 'blue' }, }); css(sheet.background); const buffer = StyleSheetTestUtils.getBufferedStyles(); });
Radium is another example. It has a TestMode object for controlling internal state and behavior during tests with the methods clearState
, enable
, and disable
.
Here, you can find an example of how TestMode is used.
Conclusion
CSS-in-JS is a technique for styling applications with JavaScript, and you can do interesting things with the libraries that implement it.
In this post, I have shown you five things that probably you didn’t know you can do with some of these libraries. Of course, not all libraries are created equal, and some things only apply to specific libraries.
Check out this playground where you can test and compare many CSS-in-JS libraries.
On the other hand, there are other libraries that are taking the concept of CSS, JavaScript, and types a little bit further.
One of these libraries is stylable, a component-based library with a preprocessor that converts Stylable’s CSS into minimal and cross-browser vanilla CSS.
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
