This article touches on the latest trends in CSS for big web applications (usually SPA). I don’t try to question whether it is the right or wrong direction, rather try to list all of them.
Originally, web pages were designed to be informational pages with hyperlinks (even images should not be inlined — it is explained by the fact that in 1990, bandwidth and computer resources were very small): something like interactive books. CSS was designed to be able to add some basic styling, and originally it seemed like a good idea to have your own personal styles, which would override the external styles. Nowadays, it is definitely a crazy idea — to try to apply your own styles to, let’s say, headers — developers definitely don’t expect it from users.
One of the funniest things is that it was pushed by Microsoft, you can read about it here — this is a very extensive article, but I’ll skip this initial part, and go straight to important milestones.
Historically, frontend was considered a second-class citizen, and very often it was implemented by designers or by special people, who often did not know a lot about programming — usually it was some template language for some backend. Back more more than 10 years ago, it was not_that_big of a problem, because the size of a typical web app was not big, so styling using classes worked_fine enough_. Not ideally, but not that annoyingly — though the interest in the Internet was growing, and applications were becoming bigger and bigger, and at some point, people started to realize that CSS has a list of problems, which were becoming more and more serious:
- specificity hell — new rules can accidentally override some old styles, and usually it happens in absolutely unexpected places
- running out of class names — in big applications, it is very easy to accidentally reuse some class name (like
- fragility — dangerous to add new rules/change old ones, because we are unsure how it might affect our project in all possible places
- big files — while it is better to keep everything in one file (so that the browser won’t spend any additional roundtrips establishing connection with our server), it is really hard to maintain one big file
- verbosity — we have to repeat many things constantly (for example, if we want to add
), and it is hard to change them lately. Same class might not work because we might want to pass some parameter
- no way to share variables with application code — we can hardcode them in both places, but if we want to change, there is a big chance of missing something
CSS is not a programming language — despite introducing variables, there is no functionality for functions, conditions and loops, so there is no way to somehow automate code generation. In case you create class names based on some properties in your code, you have to repeat all of them in your CSS code, and this is inevitable. Same is true for variables, so it is especially painful if you need to change all “green color”, to a slightly different one — sometimes colour might be the same, but semantically it is a different colour (e.g. primary vs header-title). All of this led to the development of preprocessors, languages very similar to CSS, but with extended capabilities — SASS, LESS, Stylus and PostCSS.
In computer science, a preprocessor is a program that processes its input data to produce output that is used as input to another program.
List of essential features:
- Variables (one of the most important things!) — so colours and sizes are now semantic
- Imports — they create one big file, which is better than CSS’ approach to just download all imports, establishing a new connection for each new file.
- Loops — allows to create repeated styles pretty easily, for instance, it allows you to create/change grids in an extremely simple manner.
- Functions (or, more often called
) — allows to share the same functionality with different parameters pretty easily
- Nesting — allows to keep related styles together, and removes the possibility of error when we write nesting by hand in CSS
Preprocessors definitely help with verbosity, separating styles to small files (they all encourage concatenation into one file) and keeping variables in one place, they do not help you at all with specificity hell and fragility. In order to address these issues, a number of so-called_methodologies_were invented and popularized.
All methodologies mostly tackle the problem of global class names (so possible overlapping) and specificity hell. Because CSS lacks any scoping, we have to work around this issue, using some form of class name rules. They all have a different approach, but the idea is kind of the same — we use some prefix based on some rules.
For example, let’s take a look at the BEM approach. It is more than a CSS methodology, but we will take into consideration only the naming rules. BEM stands for block, element, modifider; let’s look at a small example. For instance, we want to style the header (which appears to be the last one, which is bold) in the list inside the footer. So we have the following components:
- Block —
- Element —
- Modifier —
So, the complete classes will be
footer--list-header footer--list-header__last. It is pretty verbose, but it allows us to write meaningful class names and (to some degree) non-colliding class names. Also, you can see that the element here consists of two logical parts, so it would be nice to do so, but because we have only these 3 parts, it means we will need to separate another block,
footer-list, and inside it we can have an element
header. I understand, that a lot of people will say at this moment, that I have no idea what BEM naming is, but it is all just details with the same result – we have unique prefixes, and it helps us to avoid name collisions in exchange for verbosity. BEM approach does not encourage any nesting, and in case you need to share some styles, in the context of this approach you need to share components, not just class names. Other approaches use nesting, but they have strict rules what to nest and with which classnames.
The problem is that they solve the problem of global variables only partially. We still need to use them, and we avoid problems by making them longer using special rules, and this is not a very reliable solution in the long run. It works, though, so in case you cannot use CSS modules (which adds prefixes automatically), definitely check them out.
I’d like to point out, though, that BEM, by discouraging any nesting, solves a problem of specificity almost completely — but, as always, in exchange for the removal of the C in CSS; without it cascading is impossible (is it good or bad — another big topic for flaming).
List (non-exhaustive) of different methodologies (in no particular order):
- BEM — Block, Element, Modifier
- SMACSS — Scalable and Modular Architecture for CSS
- OOCSS — Object Oriented CSS
- ACSS — Atomic CSS
We have to step back a little before proceeding to the actual discussion of css-modules, which itself is a specification for loaders.
And because node.js was released, people started to build them.Grunt, Gulp, and then, finally Browserify. The last one is especially important, because while Grunt and Gulp perform a set of tasks on files, browserify processes everything based on a set of rules. Essentially, it means that we can process everything, including css, and it means, that we can
requirecss files, and get some output. And it works exactly this way – we require a css file, it is parsed, and we have a normal js object with keys as our classes, but values are different_unique_strings!
This is an amazing breakthrough, and I would like to stress it once again. We are able to have as many_title_classes as we want, as long as they reside in different files — which is perfectly fine for us, as we want to keep them nearby to the template/component/UI which actually uses them. CSS modules, in fact, is a concept of having scopes, it is like a block-element system from BEM, where block is unique prefix (and we don’t have to think about it at all), and element is our classes — e.g._title_. Because we can modularize as much as we want, and prefix will be unique, we don’t really need to introduce modifiers at all — but we can! We can use the whole BEM naming system again, or use nesting, or absolutely anything, but with being sure that it won’t overlap with something else. This is a very powerful technique!
The biggest downside, which is still unsolved, is sharing code between application and our styles. Using preprocessors, we can define same variables during build step, but if we want to define something during the runtime, we have to fallback to old
styleattributes, which naturally leads us to the last point – css-in-js.
There was some playing in this field before, but all the buzz started after this talk:
- there are no classes at all, so you can just tinker around changing styles of
- looking into the developer console, it is incredibly hard to understand which component is here
- we transfer more bytes — because everything is inlined, we have to repeat ourselves in many places
Example of project with inline styles
The other approach is to use DOM specification about StyleSheets. It works almost the same from the interface part,
Of course, there are several problems:
- tools are not ready yet. Thanks to the JS ecosystem and community, it is fine for Electron based editors, but more traditionals IDEs will pick it up not that soon (by tools I mean emmet and linting mostly)
- many libraries don’t have any nesting
- it is a very rapid field (as usual in JS world), so there is a big chance that in couple years your library will be obsolete and abandoned
And list (non-exhaustive) of active projects, in no particular order:
Nowadays, when you start a new web project, you should definitely choose a strategy, based on the size of the application and requirements. If you don’t have to style a lot of stuff dynamically and you know all the variables at the build step, and you don’t want to share some components between different projects with different themes, then you can safely pick a more traditional approach (but I highly recommend to use CSS-modules, to solve all problems with global class names and specificity), using some methodology of your choice, and stick with it. There is a 100% chance, that your choice will still to be sane in the next 5 years, which is a big question in the case of JS choice.
In case you need a big flexibility, heavy theming and maybe some on-the-fly style changings, then go with CSS-in-JS. You will need to evaluate all the existing solutions by yourself, picking which you believe the most, and which suits you the best — it is a risky bet anyway, but it will definitely solve a lot of traditional problems of css, especially with shared components between projects and runtime style changes.