How should we style our React app?
This is a huge and controversial topic so we need to be a bit more chatty. It's not obvious that there is (or should be) a one-for-all solution. First we set needs and propose options. Then we break down the evaluation into 3 major topics usually groupped together as "style":
- Behaviour (SMACSS state)
- Component appearence (SMACSS module)
- Cross-component appearence (SMACSS base, layout, theme)
- all the features of modern css and preprocessors (e.g. media queries)
- none of the issues of regular css (e.g. global namespaces)
- clear best practices and conventions
- styles not breaking our functionality
- the more foolproof the better
- active community, good documentation
- simplicity for small projects but scalability for big ones
- low cost of entry
- framework agnostic
- server side and react native support is a plus
- Tier 1 performance
- mixing component logic with styles
- having to use conventions we cannot test or enforce
- big cost of entry
Such as SASS, LESS, SCSS.
Pros
- feature rich
- solves issues of regular CSS
- faster
- widely used and known
- framework agnostic, easily portable
Cons
- technical concerns rest on you
- conventions, best practices not enforced
- requires conventions to be viable for bigger projects, there're many of them with no clear winner (e.g. BEM, OOCSS, SMACSS, SUITCSS, Atomic, SASS-Guidelines)
- conventions are completely separate from other project conventions (e.g. naming, structure) and have similar complexity, high cost of entry
Library comparisons:
- Generations / evolution of libs
- Comparison of libs by Michele Bertoli
- Comparison of libs by Radium
- troch/choosing-a-css-in-js-library
- CSS in JS comparison with benchmarks, articles, other resources
Out of the many libs the best choice currently seems to be styled-components
which is also clearly shown in usage trends. Pros and cons address this in particular but most apply accros many.
Pros
- feature rich (complete SASS features for JS with polished)
- no issues of regular CSS
- best practices and conventions come with the lib
- JS for logic (e.g.
:nth-child(even)
vsi % 2
) - critical parts can be unit tested
- technical concerns are abstracted away (e.g. CSS injection, object creation and structure)
- very simple for small projects
- needs structure / conventions for big projects, but those align with project conventions as a whole (great articles on the topic: Structuring our Styled Components | Part I, Structuring our Styled Components | Part II)
- doesn't exclude other ways of styling
- good documentation, active community
- low cost of entry also for those only familiar with CSS
- server side and react native support
Cons
- not framework agnostic (but uses CSS syntax which makes it somewhat portable)
- ability to mix component logic with styles
- security issue with interpolation (article describing the vulnerability, GitHub issue, currently open)
Let's address the concerns separately. There's also a great post about this on StackOverflow.
We want our state and behaviour to be defined in one place and to be unit tested. As mentioned before we don't want styles to break our app functionality. CSS in JS has the benefit on keeping state-related code in one place and allowing to test critical parts of the style instead of only testing CSS classes (e.g. disabled button, crossed out text). This talk does a great explanation on it: Michael Chan - Inline Styles, 9:39 - 15:55.
Use CSS in JS.
CSS still presents a big drawback on the convention and entry cost part. CSS in JS handles the conventions of component appearence well. It has multiple applicable cons, but they can be addressed (see Conventions).
Use CSS in JS.
Testing this is not in the scope of unit testing. For layouts best practices are estiablished and there're many libraries abstracting away most of the CSS work. Scoping is not an issue for global styles. They also tend to be more straightforward and wouldn't make use of the conventions of CSS-in-JS. CSS-in-JS still can be used to keep the way of styling unified and get the benefit of other, more advanced features such as server side rendering or critical CSS. However Preprocessed CSS modules and sometimes even regular CSS can also meet basic needs.
Use either CSS in JS or Preprocessed CSS modules or Regular CSS.
Despite our efforts there're still some custom conventions we have to establish, however now only a small number of straightforward ones.
It is very often the case that CSS-in-JS is presented with examples where it is part and parcel of the components. This gives the wrong idea and (understandably) scares off poeple. Separating the concerns of style and business logic is encouraged on many levels. Starting on component level (see: "Presentational and Container Components" by Dan Abramov) reaching down to styled-components
specifics (see: "Separate your code with Styled Components" by Sara Vieira, "Get Organized with Styled-Components" by Jeremy Davis).
Our approach is inspired by said articles, aiming to provide a clear and generic separation between styles and components.
Component styles are declared in a separate .styles.js
file using the styled
helper function. See the example below.
SMACSS categories provide a practical separation of concerns. Module and state categories are covered by the component.style.js
pattern, however styles that reach accross components (whether because they are global or just affect many components) need a proper place. Place them in src/common_styles
and use SMACSS catogires for grouping. Applies both to CSS and CSS-in-JS.
.
└── src
├── common_styles
│ └── layout.scss
└── components
└── Footer
├── Footer.js
└── Footer.style.js
Footer.js
import { TextWrapperDiv, SocialIcon } from "./Footer.style";
export const Footer = (props) => {
return <div>
<TextWrapperDiv {...props}>
// ...
</ TextWrapperDiv>
<SocialIcon />
</div>
};
Footer.style.js
import { styled } from "styled-components";
export const TextWrapperDiv = styled.div`
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
${p => p && p.secondary`
background: white;
color: palevioletred;
`}
`;
export const SocialIcon = styled.img`
width: 45px;
`
- Why We Use Styled Components at Decisiv
- React.js inline style best practices
- The ultimate CSS battle: Grid vs Flexbox
- Comparison os styling variants
- Should I use CSS-in-JS?
- CSSconf EU 2017 | Mark Dalgleish: A Unified Styling Language
Things to keep an eye on: