Note 2.0: This post has not aged particularly well due to a lot of changes to a number of the tools and frameworks referenced. I had intentions to update this with a new example project and a new post but everything keeps changing faster than I can keep up. Please keep in mind that if you do follow along you may see that a lot of things are no longer the best approach for the same result.
Note: Tailwind has had a new major release which changes a lot of what is below, if you're interested in how I've adapted given the upgrade let me know!
My favourite way of doing CSS
I wanted to share my favourite approach for handling CSS in my react projects currently. I mention a little bit about how I came to use this solution and why it was needed in the first place. I also include some code snippets to show how the different libraries and tools are used together.
The Problem
I worked with a small team of developers; most of which are primarily .NET developers. When it was time for us to start building out our newest project we wanted to make sure we could do a few things.
- Be productive as soon as possible, and for as long as possible.
- Spend less time learning technologies and more time solving problems.
- Keep styling as consistent as possible.
What this meant to us was that we would need to be comfortable across both sides of the stack as quickly as possible. At least comfortable enough so that we could be productive from the get go.
Our biggest concern wasn’t having part of the team learning JavaScript and React while the other half learned .NET Core, but how we handled our CSS. Because CSS is hard.
The Solution
Our solution was CSS In JS. I won’t cover CSS in JS in great depth here. If you are new to the idea and curious about it this is a great post.
Specifically we narrowed it down to using Emotion and Tailwind along with some Babel magic to make them best of friends.
Why Emotion
- One less build step.
- Felt most at home in JavaScript
- Dynamically change styles directly with JavaScript.
Setting up a build process is a pain and not much fun. Using CSS in JS meant that we didn’t need to worry about setting up a CSS preprocessor; Using Emotion meant all of our styles are built along with the rest of our JavaScript. And because the styles become part of the code, we can worry less about bundling unused CSS into our project as only the used CSS should be included.
Writing our styles in JavaScript feels more at home to me. Although Emotion is practically still the same as writing plain old CSS, it’s still nice not have to be jumping between multiple files when building out a new component or view. Having everything contained in the one file, and the narrowly scoped nature of CSS in JS, meant that it was easier to focus on al logic and styling of a component at any time.
In practice this:
.button {
padding: 10px;
border-radius: 5px;
background-color: blue;
color: white;
}
import * as React from 'react';
const Button = (_props) => {
return <button className="button">Click Me</button>;
};
export default Button;
Becomes:
import * as React from 'react';
import { css } from '@emotion/core';
const buttonClass = css`
padding: 10px;
border-radius: 5px;
background-color: blue;
color: white;
`;
const Button = (_props) => {
return <button className={buttonClass}>Click Me</button>;
};
export default Button;
And if we used styled component (my preferred approach), we get this:
import * as React from 'react';
import styled from '@emotion/styled';
const Button = styled.button`
padding: 10px;
border-radius: 5px;
background-color: blue;
color: white;
`;
export default Button;
Using Emotion quickly proved to be powerful way to build dynamic styles for our components. No longer did we have to write separate classes for different component states. We could just directly modify our styles based on our components state or props.
import * as React from 'react';
import styled from 'emotion/styled';
const Button = styled.button`
background-colour: ${props => props.isPrimary ? 'blue' : 'green'};
color: white;
`;
export default Button;
Why Tailwind
- Short hand is easier to remember.
- Save time on the easy stuff. More time for the challenging stuff.
- Consistency.
The biggest reason we decided to use Tailwind was because it made writing CSS accessible to our developers who had little to no experience building interfaces for the web. At least with modern frameworks like react.
Being able to use self descriptive and easy to remember class names meant that our developers could write out styles without having to know much CSS at all. This meant they had less to think about when building out simple components, saving (albeit short) time for worrying about bigger problems.
Writing this:
const button = css`
${tw('rounded text-white bg-blue')};
`;
Is the equivalent of writing this:
const buttonClass = css`
border-radius: 0.25rem
color: #fefefe;
background-color: #7070ea;
`;
While a relatively simple example, the Tailwind approach for this button class didn’t require much thought at all. If wanted the button to be rounded I would just add rounded
. If I wanted a blue background I would just add bg-blue
. It proved to be an incredibly fast way to build out presentational components. It also works just as you’d expect with ::before
and :hover
as well.
const buttonClass = css`
${tw`bg-purple`}
:hover {
${tw`bg-purple-lighter`}
}
`;
Another great bonus for having so much of our CSS basics handled by tailwind means there is a great deal of consistency on styling, as long as we are consistent in using tailwind. All of our colours and expected spacing etc etc is managed by tailwind. If we have use tailwind as expected, this means we should have consistency across our application, as well as the ability to chance these colours and values in one place (tailwind.js) and have it immediately propagate throughout the application.
Babel Macro Magic
I am sure at first glance you would have seen the follow use of tw
and been a little confused. If you missed it, here it is again:
consst Button = styled.button`
${tw`bg-purple`}
`;
This is where some Babel magic comes into play. Using the very cool Babel Macros we can use the tailwind.macro
package to import this babel tool directly into the files we want it in. If you want to check out what macros are and how they work, you can check out this video . This lets us use the tailwind classnames inside the emotion template literal strings and it gets compiled down into the CSS they represent.
Concerns
I don’t claim to think this is perfect. I do feel that by trying to obfuscate a lot of the CSS behind shortcuts with tailwind can make it harder to debug styles and near impossible for developers unfamiliar with the tooling to know what on earth is going on.
I have found that this approach can add some bloat to my component files. Declaring various styled components to be only used once tends to result in some length files. I generally make an attempt to move out styled components I find myself reimplementing across multiple components into a single module.
Final Thoughts
It’s by no means replacement for learning CSS as more complex styles require the usual CSS to be written but it does make it more accessible. Once you get a grip on the tailwind classnames it can be ridiculously fast way to build out components and pages; I find myself blocking out everything really fast only only having to go back and tweak minor things here and there greatly improving my development speed.
EDIT: I have since deleted this repository. It was out of date as both tailwind and the tailwind components package have been updated. I hope to create a new up to date example soon, and a new post showing how the set up works in more depth.
I have an 'starter' repository I made with TypeScript and Next.js you can view on my GitHub here if you're interested.
Certainly curious to hear what you think about this approach or if you have any similar alternatives!
Top comments (13)
Hey Luke, I was wondering which would be a good approach to use TW + Emotion together and this helped me a lot, I'm so interested to know how you use Tailwind with the last realease, I would love to read the article soon. Greetings!
Hi there! Great article. I'm interested in how you adapted it to the newer version, maybe could you share how you did it please?
Hey! Thanks for the comment.
I've actually got a new repo up with both the latest nextjs , tailwind and emotion versions!
github.com/lpbayliss/nextjs-starter
I'd like to revisit this article soon with the updated steps but hopefully this can help scratch that itch!
Cool, I'll check that out. Thanks!
I find this hard to create generic components with this approach. For instance, how do I create a generic button class so that I can pass in a theme prop and it generates the corresponding button for that theme? What's the best approach?
Hi,just seconding a few comments below that I'd love to see an updated version of this idea working in isolation. Really like the thought behind it!
I’ve got a new project up, and about half way through an updated post I’m hoping to publish soon!
Already an update? :)
Thanks for the article. The repo link seems dead? Would love to see your starter.
Hey! Sorry about that. I recently deleted it (as a few of the tools have since been updated and it was now out of date). I forgot I had linked it here, oops.
Hi thanks for sharing your approach! Just one question, why didn't you use the nextjs build-in styled-jsx? Just personal taste or you have found something make emotion a better choice?
I just prefer emotion over styled-jsx. I enjoy the styled component approach better.
Just came here to see if anyone had attempted this and the solution looks neat, like the best of both worlds approach. Would be keen to see the update.