A couple days ago, I posted this somewhat-snarky tweet:
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| ┻┳| ┳┻| _ ┻┳| *.*) I think CSS-in-JS is the future for #ReactJS. ┳┻|⊂ノ ┻┳|
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
It surprisingly led to some good discussion in this thread:
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
@AdventureSteady A few reasons. What React got right was tightly coupling JS & markup. Binding CSS to JS + markup has been slow b/c the tooling was terrible.
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
Unfortunately Twitter is not ideal for providing context and longer explanation, and I thought this might be a good way to follow up. Given that, a lot of this article describes what led to our decision to use CSS-in-JS, and provides specific reasons for choosing Styled Components further below. I hope what follows is useful for you and your team.
There are a lot of strong opinions surrounding CSS-in-JS, so I’d like to start with a few clarifications. If you’re already familiar with CSS-in-JS, you can probably skip this section.
I don’t want CSS do die. I’m not trying to avoid it. I don’t think it’s obsolete, outdated, or stagnant. And I certainly don’t want a JavaScript overhaul of CSS. That’s not what CSS-in-JS is about regardless of the hype.
I’m not saying Styled Components is better than Radium, Aphrodite, Glamor, Glamorous, Emotion, or any of the other CSS-in-JS libraries. All of these libs have done incredible work and solved a lot of challenging problems.
I’m also not saying CSS-in-JS is right for your team. Styled Components works really well for us, and I’d like to share why and how.
Ben Alman has this great tweet I like to reference from the early days of React.
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
Facebook: Rethink established best practices™
— @cowboy
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
When React was first announced it seemed so backwards. Colocating JavaScript with markup? Haven’t we proven this was a bad idea? What about separation of concerns?
As it turns out, the idea wasn’t bad, but the implementation needed improvement. React provided the tooling to prove the concept. As more people began using React, we realized that JavaScript and markup have the same concern and there are a lot of benefits to colocation.
React also has a built-in pattern for colocating CSS using inline styles. But inline styles didn’t feel like writing CSS, was difficult to transfer from stylesheets, and didn’t have Sass / preprocessing support. All of this created enough friction to keep styles separate. From this tension the CSS Modules pattern developed, and it’s great. Component-specific CSS / Sass files are directly imported and provide a great pattern for organizing styles.
But the problem of managing global styles remains. If you’ve been working in frontend for any time at all, you know that managing styles, especially at scale, is really challenging. BEM, SMACSS, and other CSS patterns provide a lot of great guidelines for managing styles. But guidelines only go so far. And as your application grows, it becomes more difficult to avoid unintended side-effects. Mark Dalgleish has a great talk on CSS-in-JS and says this much better here.
The premise for CSS-in-JS is that styles, JavaScript, and markup all have the same, shared concern and therefore should be tightly coupled. Problems with potential collisions are resolved by scoping styles to the component. We have a 0% chance of styles leaking. And if styles are updated in the component there are no ripple-effects across the DOM. What was once a best-practice and guideline is now strictly enforced by the nature of the tooling. A common critique of modern CSS-in-JS libraries is complexity (or at least a feeling of complexity), and that’s fair. But the appeal of CSS-in-JS is not simplicity, rather predictability and consistency.
As Glamor, Glamorous, Styled Components, Emotion, Aphrodite, etc. became available, they created a surge in popularity for colocating styles. Our implementations finally caught up the the concept. Similar to colocating JavaScript with markup, the idea of colocating styles wasn’t flawed, rather our previous tooling was inadequate. We are now able to stand at a vantage point where we can see the benefits of the concept.
Our UIs are more complex than ever and continue to trend in that direction. Not only are individual clients more complex, but it’s not uncommon for a team to support multiple web UIs and native mobile clients. As this complexity increases, so does our need for predictability. This trend raises a larger question: How do we keep UI consistent across multiple applications?
Our frontend team at Decisiv is solving this issue by building an internal component library. It allows us to version, test regressions, collaborate with our design team, and share our UI across multiple applications. A lot of other teams are doing this as well. Custom component libraries were once reserved to large dev shops and thought-leaders. But as our tooling improves, I see more teams building their own internal component libraries. Design and development is becoming less of a handoff and more continuous collaboration. (Which is awesome! 🎉)
From my experience, building a component library is the best way to keep UI consistent and predictable across applications, and CSS-in-JS has been the best tooling available to build these libs.
We were drawn to CSS-in-JS for the reasons mentioned above, but Styled Components in particular has been a great tool for our team. Below are the main reasons we chose it.
Being on the edge of a new technology is inherently risky, but we were really encouraged by the community surrounding Styled Components. The team is connected to the community and always asking for feedback. We recently started contributing back to this community by open sourcing our internal styled-components-modifiers library, and have other internal tooling we are hoping to open source when it’s ready.
Using template literals to write CSS syntax in our components is a huge boost to our team’s productivity. Developers who were brand new to CSS-in-JS could immediately hop in and start writing styles. I probably could get used to writing { fontSize: “24px” }, but having a familiar syntax is really a great feature.
We also really liked that Sass support was bundled into the lib.
const Link = styled.a`
cursor: pointer;
text-decoration: none;
&:hover {
color: blue;
text-decoration: underline;
}
`;
Again, writing our styles in this way feels really natural and reduces lines of code at the same time. Along with the basic Sass support, there’s also Polished, a small toolset created by Styled Components to provide additional Sass functionality and other helpful tooling.
Our team is also in the process of developing a native mobile app with React Native. It was really important for our UI to feel consistent on mobile, and having that bundled into Styled Components is a huge win.
These recent expressions of CSS-in-JS are still really new and far from perfect. There is still a need for established patterns and a more robust ecosystem. I listed a few in this thread:
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
@_alanbsmith Out of curiosity, what's the "almost" part there? What are you missing?
— @mxstbr
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
However, I’m encouraged to see the adoption and enthusiasm of CSS-in-JS moving it forward. I’m also encouraged by the dialog between the various lib communities. Those conversations will help establish patterns for best-practices leading us to more consistent and predictable UI.
Styled Components has been great for our team, and I think it could be really useful for a lot of other teams as well. I hope some of the ideas here were useful and thought-provoking. I’ve been thinking about this for a while now, and I have other posts queued up, so stay tuned and thanks for reading!