CSS in a growing web application is really, really hard. I'm doing CSS related projects on various side projects and my internships in New York and Vienna for a while. Every time I'm starting a new CSS project from scratch I always feel like: This time I need to do it right. The hard truth is, every CSS project I touched until now, didn't feel right, even if they had been touched by Senior Creative Developers (That's what experienced CSS enthusiasts have been called at my internship in New York). At some point the whole styling of your application just feels bloated with special rules everywhere. They either come from browser incompatibilities or designers who want to move a heading 1px to the top and 3px to the bottom. Even though most requirements from designers might seem stupid for us developers, it's the designers job to make the product, landing page or whatever else he was designing look awesome. As developers we somehow need to implement and deliver those small design tweaks, without screwing up our CSS architecture.
Besides design decisions dead styles become a real issue and bloat stylesheets for no reason. This might not be the biggest issue for small static web pages, but for growing single page applications dead CSS rules can become a pain. Especially when selectors are added dynamically to a dom node. As selectors are used in two different languages simple refactorings like renaming and extracting common styles become almost impossible. A lack of refactoring tools might lead to the wrong abstraction because it needs to be decided up front.
"Duplication is better than the wrong abstraction" Sandi Metz
Even tough preprocessors like Sass, Less provide mixins which can be used to share common styles in style declarations, the automated refactoring possibilities are still limited.
Besides inconsistent design decisions and the lack of automated refactorings, comes the pain of the language itself. (Vjeux summarizes)[https://vimeo.com/116209150] them when using CSS at scale:
- Global Namespace
- Dependencies
- Dead Code elimination
- Minification
- Sharing Constants
- Non-deterministic Resolution
- Isolation
- New Syntax/Language and dependency
Here at (Crewmeister)[https://crewmeister.com] we plan to do a redesign of our application and for that purpose we're looking for a flexible and maintainable CSS architecture which can easily evolve over time. In our opinion the new architecture should contain the following qualities:
- Easy reasoning about where styles are coming from
- Avoid selector clashes
- Special cases are easy to implement
- e.g. Second item in a navigation needs to be bold
- Application | Pricing | Features | Login
- e.g. Second item in a navigation needs to be bold
- Portability of our components
- use same menu bar in the app, our blog and our landing pages
- Consistent Styling Possible (DRY Styles)
- automatically add vendor prefixes to our css
- variables
- (fast unit tests for our styling)
- element A needs to look exactly like element B
During my research I found the following four CSS architectures, which looked interesting for us:
As Chrome and Firefox recently implemented CSS variables, one killer features of CSS preprocessors is natively available in modern browsers. Even tough IE and Safari don't support them yet, PostCSS with the CSS next plugin port this syntax to incompatible browsers.
Naming is hard, especially on a global context in the application. To avoid naming clashes many naming conventions have been created. Hiljá Studio gives an overview of different naming conventions.
- Block Element Modifier (BEM)
- Object Oriented CSS (OOCSS)
- Scalable and Modular CSS Architecture (smacss)
- Atomic CSS
- easy to get started (when plain CSS is used)
- integration is easy
- designers/developers are used to it
- autoprefixer can be integrated
- Browser developer tools can be used
- cache and gzip files
- new language as a dependency
- global css declarations (name clashes)
- portability is hard
- js and css files need to be included
- dead code elimination is hard
- automated refactoring tools are missing
- http://postcss.org (nodejs)
- http://lesscss.org (nodejs)
- http://sass-lang.com (Ruby or C implementation)
CSS modules are an unofficial specification of a CSS extension which solve the global context of CSS.
- A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. * (CSS modules)[https://github.com/css-modules/css-modules]
CSS modules make it possible to import stylesheets into a JS file. Under the hood the imported selectors are replaced automatically by a global unique selector. Because CSS modules are neither an official standard nor implemented by any browser, a build tool like webpack or browserify (with (CSS loader plugin)[https://github.com/cheton/browserify-css]) is required.
// https://github.com/css-modules/css-modules
import styles from "./style.css";
// import { className } from "./style.css";
element.innerHTML = '<div class="' + styles.className + '">';
CSS modules only solve the global CSS issue. If variables or automatic vendor prefixing should be part of the architecture a separate CSS preprocessor is needed.
- no global CSS anymore
- Browser developer tools can be used (with source maps)
- min. 2 dependencies required
- CSS Preprocessor
- JS build tool (Webpack, Browserify + Plugin)
- new language as a dependency
- portability is hard
- js and css files need to be included
- dead code elimination is hard
- automated refactoring tools are missing
Inline styles seems to be the new trending topic in the js community. It allows the usage of one unified language to generate styles, markup and business logic. Styles are generated by js objects which are transformed to inline css rules. Those rules don't support media queries and pseudo elements (:hover, :before, ...), which need to be reimplemented in the browser. There are libraries like (radium)[https://github.com/formidablelabs/radium] which solve this issue for react applications. For that purpose radium is reimplementing media queries and pseudo elements in JS.
- no global context
- no new language dependency
- JS refactoring tools can be used
- dead code can be detected
- styles can be calculated during runtime
- e.g.. custom color scheme for ever customer, without generating a new stylesheet on the server
- prototyping/debugging with dev tools is more difficult
- bloated HTML
- readability of rendered source code is hard
- no caching of css files
- designers might not be familiar with js
- No media queries
- No pseudo classes
- performance is an issue
- http://jsperf.com/class-vs-inline-styles/2
- http://jsperf.com/classes-vs-inline-styles
- http://jsperf.com/inline-style-vs-css-class/9
- Server side rendering could fix this
- autoprefixer
- could be used at runtime (performance?)
Stylesheets generated JS can be seen as a new kind of CSS preprocessor. Stylesheets can either be generated on the server or during runtime on the client. This enables browser caching of styles and gzip compression. As Styles are written in JS automatic refactoring tools can be used which can eliminate dead code.
- no global context
- no new language dependency
- IDE refactoring tools can be used
- dead code can be detected
- styles calculation during runtime is possible
- e.g. special color scheme for a customer, without generating a new stylesheet
- autoprefixer can be integrated
- cache and gzip files
- prototyping/debugging with dev tools is more difficult
- designers might not be familiar with js
CSS was never built with web applications in mind. As styles are getting more complicated we need to find a convenient way to deal with styling concerns. "JS generated stylesheets" solve many pains of CSS. In our case we can use the tools we already know and don't need to introduce a new syntax/context into our stack. We just need to include a new library over npm and for the first face of the redesign thats all. Because we already minify our JS we don't need to add a dedicated minification step for our styles. JS became the main language we're speaking on the client, which enables us to use refactoring tools to easily rename wrongly spelled 'selectors', extract common styles and remove dead code.
We just started adopting this pattern in our application and for now this looks really promising. We still need to solve auto vendor prefixing and server side generation of our styles, but I think those are issues which can be solved.