In any non-trivial React app, CSS styles can become a problem if you don’t manage them correctly.
Global style definitions, !important
rules everywhere, and low flexibility when reusing components are examples of these problems.
This has led to alternative approaches to the classical approach of using CSS files. For example, here’s a good post about three styling approaches.
This article is about inline styles. However, I’m not going to talk about what they are or whether you should use them or not.
I’m going to talk about libraries that will help you use inline styles in your React application — libraries that allow you to use features that are not directly supported otherwise (like media queries).
I’ll compare:
This list is the result of not considering libraries that:
However, as with most articles of this type, you may not agree with this list so feel free to post a comment with your favorite library and what’s special about it.
Using each of those libraries, I’m going to style a div element with the following CSS rules:
background-color: #00ffff; text-align: center; width: 100%; padding: 20px; :hover { color: #ffffff; cursor: 'pointer' } @media (max-width: 700px) { background-color: #ff0000; }
This will be the result:
At the end of this article, you’ll find a table summarizing the features of the libraries.
Let’s start with Radium.
Of the three libraries presented here, Radium is the most popular one in terms of GitHub stars, issues, and StackOverflow questions.
If you already have some inline styles as object literals, you don’t have to modify them to use Radium.
For example, here’s the style object that corresponds to the CSS rules shown earlier:
const styles = { panel: { backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', ':hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } } };
It looks like a regular inline style, except for the hover and media rules, which are not supported by common inline styles.
If you apply this style to a component:
class App extends Component { render() { return ( <div style={styles.panel}> React rocks! </div> ); } }
And run the app, the div
element will change its style but it will ignore the hover and media rules. Actually, this warning will be shown in the console:
Warning: Unsupported style property @media (max-width: 700px). Did you mean @media (maxWidth: 700px)?
To use Radium, you first have to install it:
npm install --save radium
And then import or require it:
import Radium from 'radium'; // Or const Radium = require('radium');
Radium is a higher-order component (HOC).
It processes the style
attribute of the components specified in the render method, adding handlers that update the state if interactive styles (like hover), applying vendor prefixes, and merging styles among other things.
You can use it this way:
class App extends Component { // ... } export default Radium(App); // Or App = Radium(App);
Or with ES7 decorators:
@Radium class App extends Component { // ... }
Usually that will be enough, but if you’re using media queries, keyframes, or some Radium plugins, you also need to use the StyleRoot component to wrap the top-level component of your app:
ReactDOM.render( <StyleRoot><App /></StyleRoot>, document.getElementById('root') );
Now if you run the application, and inspect the div
element, you’ll see this:
<div id="root"> <div data-radium="true"> <div class="rmq-f7d82907" data-radium="true" style="background-color: rgb(0, 255, 255); text-align: center; width: 100%; padding: 20px;"> React + Radium rocks! </div> <style> @media (max-width: 700px){ .rmq-f7d82907{ background-color: #ff0000 !important; } } </style> </div> </div>
Notice that for the media query, it created a new CSS class with a random name within a style
element. According to the documentation, it does it this way so media queries work correctly with server-side rendering.
And what about the hover effect?
When you mouse over the element, its style
attribute is updated to add the specified hover style:
You can also specify an array of styles in the style
attribute. This comes in handy when you need to override some styles depending on the value of a property, for example:
const styles = { panel: { ... }, alert: { backgroundColor: 'red' } }; class App extends Component { render() { return ( <div style={[ styles.panel, this.props.alert && styles.alert ]} > React + Radium rocks! </div> ); } }
There’s also a Style
component that renders a style element, which, for example, helps you add CSS rules to the body
element or scope classes applied to other elements.
For instance, this:
<div className="local-scope" style={styles.panel}> React + Radium <span>rocks!</span> <Style scopeSelector=".local-scope" rules={{ fontWeight: 'bold', span: { textTransform: 'uppercase' } }} /> <Style rules={{ body: { backgroundColor: 'black' } }} /> </div>
Will render:
<div class="rmq-f7d82907 local-scope" data-radium="true" style="background-color: rgb(0, 255, 255); text-align: center; width: 100%; padding: 20px;"> React + Radium <span>rocks!</span> <style> .local-scope { font-weight: bold; } .local-scope span { text-transform: uppercase; } </style> <style> body { background-color: black; } </style> </div>
On the other hand, one disadvantage of Radium is that only three states are supported: :hover
, :foucs
, and :active
, and if you have more than one element in your component that uses one of these states, you need to provide a unique key prop.
There’s even a function that allows you to query these states, getState:
<div style={styles.panel}> React + Radium rocks! { Radium.getState(this.state, null, ':hover') ? ( <span>Yeah!</span> ) : null } </div>
However, this means that you have to manually implement pseudo-selectors like :checked
, :last
, :before
, or :after
.
Another missing functionality is the support of @font-face. However, Radium implements almost all of its functionality as a plugin and you can also use the plugin API to implement custom functionality, like @font-faces
.
A Radium plugin is a function that accepts a PluginConfig
object and returns a PluginResult
object and it is called once for every rendered element that has a style attribute.
Here you can take a look at the source code of the plugins include in Radium. And in this repository by Ian Obermiller, you can find another example of a plugin.
Here’s the basic example implemented with Radium:
Aphrodite is another popular library for writing CSS in JavaScript, but it takes a slightly different approach than Radium.
First, install it with:
npm install --save aphrodite
Once again, you have an object with the styles of the application:
{ panel: { backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', ':hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } } }
But this time, you have to pass this object to the function StyleSheet.create
:
const styles = StyleSheet.create({ panel: { backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', ':hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } } });
In turn, the styles from this object are passed to the css
function, and the result is used in the className
property of the component:
import { StyleSheet, css } from 'aphrodite'; const styles = //... class App extends Component { render() { return ( <div className={css(styles.panel)}> React + Aphrodite rocks! </div> ); } }
Notice that Aphrodite uses the className
property, not the style
property.
To understand this, if you print the result of css(styles.panel)
you’ll get a class name. In my case I got panel_w3twfb
.
If you take a look at the object returned by StyleSheet.create
, you’ll see something like:
{ panel: { _len:188, _name:"panel_w3twfb", _definition: { backgroundColor:"#00ffff", textAlign:"center", width:"100%", padding:"20px", ":hover": { color:"#ffffff", cursor:"pointer" }, "@media (max-width: 700px)": { backgroundColor:"#ff0000" } } } }
That function returned an object that wrapped the CSS rules and added a _name
property, with the same value as the one returned by the css
function.
Using the Inspector tool of your browser, you’ll see the definition of this panel_w3twfb
class:
.panel_w3twfb { background-color: rgb(0, 255, 255) !important; text-align: center !important; width: 100% !important; padding: 20px !important; }
By default, Aphrodite appends !important to all the CSS rules. If you don’t want this, the only thing you need to do is import aphroidte/no-important
instead of aphrodite
:
import { StyleSheet, css } from 'aphrodite/no-important';
Also, when the flag NODE_ENV
is set to production
or if you call minify(true)
before StyleSheet.create
:
import { StyleSheet, css, minify } from 'aphrodite'; minify(true); const styles = StyleSheet.create({ //... });
Aphrodite will only keep the hash in the name of the CSS class. In this case, w3twfb
.
But where’s this class? It’s not defined next to the div
element or anywhere else in the body of the document.
Aphrodite creates a style
element inside the document’s <head>
element to put its generated styles:
<html lang="en"> <head> ... <style type="text/css" data-aphrodite=""></style> </head> <body> ... </body> </html>
However, you can create a style element with the data-aphrodite
attribute, and Aphrodite will use it instead of creating one.
Aphrodite will buffer writes to the style to avoid many DOM modifications. If you calculate the element’s dimensions in componentDidMount
or componentDidUpdate
, the documentation recommends using setTimeout
or flushToStyleTag
to ensure all styles are injected correctly.
And what about the hover style and the media query?
They are also added:
.panel_w3twfb:hover { color: rgb(255, 255, 255) !important; cursor: pointer !important; } @media (max-width: 700px) { .panel_w3twfb { background-color: rgb(255, 0, 0) !important; } }
In a similar way than Radium, you can combine more than one style:
const styles = StyleSheet.create({ panel: { ... }, alert: { backgroundColor: 'red' } }); class App extends Component { render() { return ( <div style={css( styles.panel, this.props.alert && styles.alert )} > React + Aphrodite rocks! </div> ); } }
Also like Radium, it has limited support for nesting. For instance, this issueshows an example of a Saas-like class for an element that has both classes, jump-btn
and disabled
:
.jump-btn { width: 32px; height: 32px; background: url('jump_btn.png') no-repeat 0 0; &.disabled { background-position: -32px 0; } ... }
That has to be written in Aphrodite in the following way because only pseudo selectors and media queries can be nested:
const styles = StyleSheet.create({ jumpBtn: { width: 32, height: 32, background: 'background: url('jump_btn.png') no-repeat 0 0', }, disabled: { backgroundPosition: '-32px 0', } }); const classes = css(styles.jumpBtn, isDisabled && styles.disabled);
However, unlike Radium, Aphrodite support font faces:
const raleway = { fontFamily: "Raleway", fontStyle: "normal", fontWeight: "normal", src: "local('Raleway'), local('Raleway-Regular'), url(https://fonts.gstatic.com/s/raleway/v12/1Ptug8zYS_SKggPNyCMIT5lu.woff2) format('woff2')" }; const styles = StyleSheet.create({ panel: { fontFamily: [raleway, 'sans-serif'] ... } });
After and before pseudo-elements natively (with the caveat that the content property requires double or single quotes inside the string value):
const styles = StyleSheet.create({ panel: { ... ':after': { content: '" Aphrodite!"', } });
On the other hand, Aphrodite doesn’t provide an API the way Radium does. Granted, it works in a different way, but it may be helpful for some situations (for example, see here and here).
It provides an extension mechanism, but currently, it only allows you to generate new selectors based on the specified styles (it’s used by the library to handle media queries and pseudo elements/classes).
Here you can see the basic example implemented with Aphrodite:
Recently, Kent C. Dodds deprecated Glamorous (a library that otherwise would have made into this list) in favor of Emotion. His reasons:
That tells us something about this library, right?
First, install it with:
npm install --save emotion
One more time, starting with the object that contains the styles of the application:
{ panel: { backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', ':hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } } }
You only need to modify the format of the :hover
pseudo class. From:
':hover': { ... }
To:
'&:hover': { ... }
Emotion is similar to Aphrodite. Both use the className
property and a function called css
:
import { css } from 'emotion'; const styles = { panel: { // ... } }; class App extends Component { render() { return ( <div className={css(styles.panel)}> React + Emotion rocks! </div> ); } }
The css
function returns the name of the CSS class that is generated automatically. In my case, it returned css-4k75yl
.
The rendered HTML looks like this:
<div class="css-4k75yl"> React + Emotion rocks! </div>
And you can find the definition of this class inside the document’s <head>
element:
<style data-emotion=""> .css-4k75yl { background-color:#00ffff; text-align:center; width:100%; padding:20px; } </style> <style data-emotion=""> .css-4k75yl:hover { color:#ffffff;cursor:pointer; } </style> <style data-emotion=""> @media (max-width:700px) { .css-4k75yl { background-color:#ff0000; } } </style>
Optionally, you can also add a label property to the style object to append a custom name to the generated CSS class.
For example, the following definition:
const styles = { panel: { backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', '&:hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' }, label: 'my-name' } };
Will result in the class name css-4k75yl-my-name
.
So in this way, Emotion is not that different from Aphrodite:
But something about this library is that it has way more features than Radium and Aphrodite.
For example, with Emotion you have two more ways to style components.
Instead of an object literal, you specify the style as a tagged template:
const style = css` background-color: #00ffff; text-align: center; width: 100%; padding 20px; &:hover { color: #ffffff; cursor: pointer; } @media (max-width: 700px) { background-color: #ff0000; } `; class App extends React.Component { render() { return ( <div className={style}> React + Emotion rocks! </div> ); } }
Notice that the syntax is completely different. It’s more like CSS:
On the other hand, you can also style elements or components with the styled function.
For this, first, you need to install react-emotion
(or preact-emotion
if you’re using Preact):
npm install --save react-emotion
Then, calling the function by first passing an HTML tag or React/Preact component and then either an object literal with the styles:
import styled from 'react-emotion'; const MyDiv = styled('div')({ backgroundColor: '#00ffff', textAlign: 'center', width: '100%', padding: '20px', '&:hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } } );
Or a template literal:
const MyDiv = styled('div')` background-color: #00ffff; text-align: center; width: 100%; padding 20px; &:hover { color: #ffffff; cursor: pointer; } @media (max-width: 700px) { background-color: #ff0000; } `;
And use that new styled component like any other:
class App extends Component { render() { return ( <MyDiv> React + Emotion rocks! </MyDiv> ); } }
Of course, props can be passed to this component and you can change its styles of based on the props:
const MyDiv = styled('div')(props => ({ backgroundColor: props.bc, textAlign: 'center', width: '100%', padding: '20px', '&:hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' } })); class App extends Component { render() { return ( <MyDiv bc='#00ffff'> React + Emotion rocks! </MyDiv> ); } }
In the documentation page for styled components, you can check out more configuration options.
You can also combine styles:
const styles = { panel: { ... }, alert: { backgroundColor: 'red' } }; class App extends Component { render() { return ( <div style={css( styles.panel, this.props.alert && styles.alert )} > React + Emotion rocks! </div> ); } }
And Emotion will merge them in the order they appear (notice the class name changes):
.css-17nr31q { background-color: #00ffff; text-align: center; width: 100%; padding: 20px; background-color: red; }
However, unlike Aphrodite, when combining multiple class names, Emotion provides some advanced options.
Also unlike Aphrodite (but similar to Radium), Emotion allows you to specify global styles easily:
import { injectGlobal } from 'emotion' injectGlobal` body { background-color: black; } ` // Or injectGlobal({ body: { backgroundColor: 'black' } }); view raw
Emotion supports server-side rendering and keyframes like the other libraries, but it has better support for nested selectors and something unique is the support for themes, provided by the library emotion-theming.
For example, after installing emotion-theming
with:
npm install --save emotion-theming
You can put the background color style in a theme to share it across other components:
import styled from 'react-emotion'; import { ThemeProvider } from 'emotion-theming'; const theme = { backgroundColor: '#00ffff' }; const MyDiv = styled('div')(props => ({ backgroundColor: props.theme.backgroundColor, textAlign: 'center', width: '100%', padding: '20px', '&:hover': { color: '#ffffff', cursor: 'pointer' }, '@media (max-width: 700px)': { backgroundColor: '#ff0000' }, })); class App extends Component { render() { return ( <ThemeProvider theme={theme}> <MyDiv> React + Emotion rocks! </MyDiv> </ThemeProvider> ); } }
Here you can see the basic example implemented with Emotion using an object:
And here you can see it implemented using a tagged template:
A good page to learn how popular are these libraries is npm trends. Here’s a snapshot of the statistics at the time of this writing (here you can find the most recent ones):
And here’s a summary of the features of these libraries:
Evaluated Aspect | Radium | Aphrodite | Emotion |
---|---|---|---|
License | MIT | MIT | MIT |
Latest version (at the time of this writing) | 0.24 | 2.2.2 | 9.2.3 |
Size | 17.9k (gzipped) | 19.1k/6.15 (gzipped) | 14.4k (core)/5.58k (gzipped) |
Documentation | 9.0/10.0 | 8.5/10.0 | 9.0/10.0 |
Syntax type | object literal | object literal | object literal/template literal |
CSS type | styles classes (for some cases) | classes | classes |
Autoprefixing | Yes | Yes | Yes |
Server side rendering | Yes | Yes | Yes |
Pseudo classes (:hover ; nth-child ; etc) |
Only supports :hover ; :focus , and :active |
Yes | Yes |
Pseudo elements (::after ; ::before , etc.) |
No | Yes | Yes |
Media queries | Yes | Yes | Yes |
Nesting | Yes | Limited | Yes |
Keyframes | Yes | Yes | Yes |
@font-face | No | Yes | Yes |
Native support for themes | No | No | Yes |
API | Yes | No | No |
If you ask me, all things being equal, I’d say my favorite library is Aphrodite.
I feel this library has the best balance. Radium lacks some features while Emotion, although it’s flexible and it has great documentation, it has so many features that it’s a bit complex to use sometimes. If you ask me, all things being equal, I’d say my favorite library is Aphrodite.
But when deciding what library to use in your project, the most important thing is to take into account the aspects that are relevant for your project. Don’t choose the library with the most features or yeses in the table.
Finally, also check out this repository where Michele Bertoli compares a lot more libraries for React and implement an example with each of them.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.