The main goal of this blog entry is to present how one could combine webpack/npm/BEM/Sass to write a modular, more complex, and React friendly CSS (Cascading Style Sheets).
The targeted group for the blog post are people working with web design and/or front-end development. I also start from the assumption that you are currently working with React, or are planning to do so, and would like to learn more about how to integrate CSS in your React-based project.
A secondary goal is to review the inheritance, cascading and scoping so-called “issues” of CSS. These are often brought up lately as hinders in a module-based environment, especially by the promoters of CSS in JS libraries, as styled-components.
Today’s web development is concerned with modularity. In this respect, React comes with the principle of components, which allow us to split the UI into independent, reusable pieces, and think about each piece in isolation. Coming back to the main goal of this post, I will show the approach we took, to create a modular CSS that will fit this modular model. To mark here is that we use the terms modules and components as similar principles.
The starting point for me was an environment with webpack, Node.js/npm and my knowledge and previous experience with BEM/Sass. The front-end developers in our group ̶ Reporting, working with the Reportal application ̶ have already chosen webpack and Node.js/npm. The reasons for this choice does not make a point in this entry; and for many this might be obvious. The questions I am going to try to answer in this context is: how will CSS fit in this environment, what libraries will be necessary and can CSS be structured to be modular? Is it necessary to use CSS in JS libraries at all? What I found out is that, yes, we can write a modular CSS that fits well in this environment and follows the modular structure of React. In what follows I will present how we can map the React files with their styling.
CSS modules mapping JS modules
Following the modular structure, each JS module should be somehow connected to its necessary style sheets. As we use webpack as a build tool, it is important to mention that webpack treats all content, css and js files alike, as modules. By using style-loader and css-loader packages, it is possible to embed stylesheets into a JS bundle. Style-loader deals with the require statements in JS files, while css-loader resolves @import and URLs in the CSS files. In this way, one can create CSS modules that reflect the React component structure. The JS component loads only the needed CSS.
In the JS files, one would write require ‘./path-to-css-file/css-file.css’ or if using ES6, import ‘./path-to-css-file/css-file.css’. The CSS files can be loaded as modules and placed in the same folder as the JS files. Ideally, every React module has its own CSS folder containing all the styling it needs to be stand-alone. This deals however only with the micro perspective, where the modules are seen as independent entities, ready to be plugged in a project.
At the project level, which I will be calling in this post as the macro-perspective, the CSS modules are not exclusively mapping the JS modules. To understand the context better, I will give you an example from an application we are currently working with. Let us call it R2. In the R2 application, we have a filter that comprises three components: one for the hierarchical filtering, one for data filtering and one for the filter panel.
From a CSS perspective, there is no point in splitting the styling in three CSS modules, as the three modules have very much styling in common. To avoid duplication of code, the three filter modules, share the same CSS file.
Writing modular CSS with BEM
BEM is a widespread and largely used methodology at the time of writing this blog post. However, I will invite you to have another look at the definition of BEM, this time with the purpose of referencing the BEM concepts to the ones of the React components.
“BEM (Block, Element, Modifier) is a methodology that helps with creating reusable components and code sharing in front-end development.”
“BEM (Block, Element, Modifier) is a component-based approach to web development. The idea behind it is to divide the user interface into independent blocks. This makes interface development easy and fast even with a complex UI, and it allows reuse of existing code without copying and pasting.”
The main key points to extract from here are: BEM is a component-based approach; it divides the user interface into independent blocks; and allows reuse of code.
Now let us have a look at the definition of the React components:
“React Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.”
The definition of React components, mentions independent and reusable pieces of UI too. As such, we conclude that BEM blocks and React components have similar purposes, when it comes to modularity. What is considered as block in BEM is the equivalent of a component in React. In both cases, we have “A functionally independent page component that can be reused.”
As we normally are concerned about the maintainability of our application over a indefinite period, we do not like to make our applications dependent on more libraries and frameworks than necessary. In this spirit, is worth remarking that BEM is a methodology and not yet another library. It offers guidelines for how to structure and organize the CSS to be modular. This means we use pure CSS, for CSS is flexible enough to allow for a modular structure. Talking about the flexibility of CSS, brings us inevitably to the debate around the inheritance and cascading features of CSS.
Global scope, inheritance and cascading in CSS
The promoters of CSS in JS (by this, I am referring to styled-components) bring often up the cascading and inheritance aspects of CSS as two major issues when it comes to writing a modular CSS. In their arguments, they seem to have a wrong understanding of what these are. This is the reason why it might be useful to remind ourselves what inheritance and cascading mean and what is their purpose in CSS. Moreover, I will be arguing for these being two very important and useful characteristics of CSS, especially for creating an accessible and consistent interface.
When it comes to inheritance, the idea is that some properties applied to an element, will be inherited by that element’s children, and some will not. For example, it makes sense for font-family and color to be inherited. This makes it easy to set a site-wide base font by applying a font-family to the <html> element. It is possible than to override the fonts on individual elements where needed. It would be annoying to have to set the base font separately on every element. This is also important for the consistency of the UI design. However, it does not make sense for margin, padding, border, and background-image to be inherited. Imagine the styling/layout mess that would occur if you set these properties on a container element and had them inherited by every single child element, and then had to unset them all on each individual element.
When it comes to cascading, it might be useful to remind ourselves that the cascade is a fundamental feature of CSS. It lies at the core of CSS, as stressed by its name: Cascading Style Sheets. The reason for cascading property of CSS is that it allows several style sheets to influence the presentation of a document. This is very important feature for theming or for adapting the styles to different kinds of devices, media or for creating an accessible and universal design for our applications. The final style for an element can be specified in many different places, which can interact in complex ways. This complex interaction makes CSS powerful, but it can also make it confusing and difficult to debug. To not get overwhelmed by this complexity, we need to be methodological in how we work with CSS.
CSS gives us the global scope so that styling declaration concerned with typography, colors, definitions of shadows, borders, paddings/margins, media queries etc. can be shared between our components. With this purpose, we have packages/files that contain styles to be shared between all our Confirmit applications, or within one application. Having shared styles assures for consistent GUI/UX and avoids duplication of code. At this point, we have to differentiate between styles that are to be shared globally and styles that have a local scope. Moreover, use of methodologies and naming conventions are needed for avoiding collision between what is local and what is global.
Avoiding CSS collisions
There are several ways one could avoid collision coming from inheritance and cascading. One of these will be to follow the BEM’s principles like:
- using unique CSS classes per entity
- not use ID selectors or tag selectors
- minimizing the number of nested selectors
- using the BEM class naming convention in order to avoid name collisions and make selector names as clear as possible
- relying on composing/mixing instead of inheritance
For a more detailed explanation of these principles, please refer to the documentation of BEM.
Another solution for avoiding inheritance and cascading issues is local scoping. This again can be achieved in different ways. In the case of our project, the solution was to establish some naming conventions and to prefix the selectors with the name of the project/package.
As an example, we have a CSS package with common styling for all Confirmit applications. We want the CSS to be scoped so that it do not collide with the CSS in the individual applications. For this, we use the co- prefix on all the selectors.
This works well in our case, as we know the context where this package is going to be used, we have in-house people working on our projects and have a good communication between us.
However, there are cases where the context is unknown. For different reasons, it might also be difficult to impose discipline in following these naming conventions. For these situations, one might want to consider solutions where the selectors are assigned identifiers generated by default, and as difficult to replicate as possible. With this purpose, one could use CSS modules. If using Webpack, css-loader package comes with CSS Modules enabled.
CSS in JS
My general opinion about writing CSS in JS is that:
- It does not scale (for covering all the CSS needs in a large project).
- It bloats the script files with styles, and makes the code difficult to maintain.
- Breaks the good principle of separating the presentation from content brought with the invention of CSS.
However, we have a case where we see writing CSS in JS as useful. This case is the one when we need to dynamically change some interface elements through user input.
As an example, from our R2 application (see images below), we have the case when we want our users to be able to change the background color of the header of the application’s dashboard. The user defines the colors in the editor, through a Domain Specific Language that we created with this purpose. The color is given as value for the CSS property, background-color, in this styled component. The styling is than applied by wrapping the styled component, DashboardToolbar in this case, around the output of the Toolbar component.
In the case of our Confirmit applications, we found out that we could continue to use Sass and BEM, that we were used with from before, to write a modular and React friendly CSS.
When it comes to writing CSS in JS. The main function that this approach has is to map the CSS to JSX elements, for scoping purposes. However, we would like to have the content separated from the presentation, and we still need to have CSS in the global scope. In these cases, solutions like CSS modules and naming conventions are better suited for us.
Another take-home message is that CSS is very flexible and gives us much freedom and power, but without a good methodology in place, internal standards and good communication between the programmers, the code can get messy, and thus get unexpected result. This has some implication on the way we work with CSS as front-end developers. We need to reconsider a workflow where CSS is an after-thought, something to be quickly done with, so that we can focus on more important and crucial things that have to do with functionality.