Skip to content

Paulmicha/css-organization

Repository files navigation

CSS organization

The problem we're trying to address here is how to design an open-source, reusable component library aiming at building (living) styleguides and design systems.

Design systems ?
See @danielmall's research, and these examples:

Problem & Solution

@MarcoSuarez's Designed for Growth article describes most eloquently what motivates this research :

As tech companies grow and age you begin to feel the debt you’re acquiring, not financial debt, but technical and design debt. Debt is acquired by building for the short-term. The interest accrued is the amount of time to manage, repair, rewrite, and build upon the poorly written and designed code.

Design debt is acquired by design teams creating non-reusable solutions for isolated problems. Design debt is made up of an over abundance of non-reusable and inconsistent styles, treatments, and interactions and the interest is the impossible task of their management and modification.

Debt is a weight around your neck. Its existence cost time, resources, and risk which prevents quality products from being built at a timely pace. By having a system, and yielding to it, we’re able to move more quickly and build better products.

Instead of creating more custom solutions, which adds to our debt, create solutions to be fed back into the system. If everything we create uses the system or feeds back into it then all design becomes systems design.

Here's the plan

Maintain 2 projects in parallel :

  1. A base, generic design system providing guidelines and corresponding "low-level" components - also serving as an example/case-study/documentation of the process of integrating reusable components in such systems
  2. Some tool(s) to generate reusable components (not restricted for use in the generic design system in • 1, see interoperability in individual components below), progressively made available in the following forms :
    • a command line generator
    • a web-based interface to create, modify, or extend components (to discuss)
    • a desktop app essentially wrapping the web-based interface (e.g. in Electron)

Here's what the workflow would look like :

  • start with a solid typographic fondation (screen-optimized font-families, color - base font size, line height, comfortable measure or line length)
  • init generic utils (optional type scale with some box-model generic spacing utilities)
  • init hierarchy (e.g. headings and smallprints, with or without using a type scale)
  • then integrate : personalize, extend desired reusable components into specific and/or private project front-end systems (goal : personality, difference)

The tooling could also have some synchronizing mecanism to fetch/upload our own, private and/or public, open-source reusable components to/from external repositories, ideally integrating by default Github, Bitbucket and Gitlab support.

Current status

The purpose of the research at this stage is to detail what would an ideal final result be. Later on, we may move on to analyze concrete options, challenges and compromises. But for now, we'll start with a list of guiding principles - here's roughly what the methodology looks like :

  • Distilled from experience, the very nature of existing guidelines and principles already makes them blueprints of and/or pointers to potential ("ideal") solutions.
  • The general value and relevance of principles, tools and references is evaluated with factors related to communicability, developer experience (and of course, fitness for purpose).
  • Their curation (collection, selection) involves criterias such as maturity, "history", standard-compliance, portability, and adoption.

Source code in this repository

The source code included in this repository is generously allowed to be made publicly available under the MIT license by Chouette - Institut de français.
It is meant to provide context in the form of a case study to evaluate and discuss the present research, and it also serves as an example of CSS organization using PostCSS.

Tools

Installation

npm install

Usage

# Watch (default Gulp task)
gulp

Principles

References :

General principles :

  • DRY / Single Source of truth - warnings : don't DRY if it's repeated coincidentally, just avoid duplicating data in source (repetition in compiled code is fine).
  • Single Responsability (context encapsulation, composability) : do one thing, and one thing well - break into individual concerns.
  • Separation of concerns - notably : don't bind JS onto CSS classes
  • Immutability - the only time to use !important
  • Open/Closed principle : never change anything at its source, always make change via extension - possibly the most useful principle for dealing with other people's code.
  • Orthogonality : avoid collisions - ex: using proper scoping
  • Moustache Principle : just because you can, it doesn't mean you should.
  • Make it readable : work towards having a predictable architecture
  • Make it easy to digest : concisely explain the reasoning behind a solution (document the why, not the what)
  • Avoid distraction : don’t code "your way" - just follow the coding standards. Make your code predictable and easy to read by coding the way people expect.

Component-oriented, modular design principles :

  • Establish a language foundation : The agreed-upon name determines how the element will be built and encourages consistent usage across the team - "If you don’t get agreement up front, prepare for more work later."
  • Depth of Applicability : number of generations that are affected by a given rule. The further the distance from the parent to the deepest descendent element, the more complex and rigid the HTML structure needs to be for the selectors to work.
  • Component Boundaries : if the component is more than 3 levels deep, it might be up for breaking apart into smaller components.
  • Shell/Content Pattern : often, there’s a shell (container), and then the content that goes within that - can be a great way to recognize when to break things down from one larger component into a few smaller components.
  • Repetition is better than the wrong abstraction : when in doubt, keep components separated (duplication of code) :
    • Appearance – If something looks visually very different it’s probably best to create two separate components, even though they might share the same content model.
    • Purpose – if two things look visually alike but actually serves different purposes, ask yourself: Are they just coincidentally similar right now? Do you see them develop in different directions over time? If the answer is yes, maybe it better to create two separate components.
    • Content model – Do they share the same content model and look similar and have a similar purpose, then maybe you can come up with a more abstract name and merge the two components into one.

Frameworks

The organization of CSS in various libraries or frameworks usually aims to avoid common pitfalls of the cascading part of CSS in modular design systems (i.e. preventing the accidental bleeding of rules).

@dakotaleemartinez provides an example of a problematic Bootstrap 3 navbar component extension, illustrating some of the pains these approaches aim to ease. @ahfarmer's article gives a categorization of the tooling available as of 2016/04/16, and @fat's talk touches upon the origins of CSS, putting its evolution and uses in perspective - along with @zackbloom's "The Languages Which Almost Became CSS".

I find there are 2 types of approaches to composable, modular design in CSS :

  • Class naming conventions - ex: eCSS, ITCSS, etc (see below).
    Such conventions already allow for implementing robust organization, and for projects that aren't going to need the kind of scaling that eCSS, ITCSS and the like are providing, we still can have the flexibility of a SMACSS-based approach, which means either picking classes or child selectors where appropriate - provided any potential "bleed" is at least documented and/or its scope really narrow.
    This is the approach currently favored here, but we may find opportunities for complementary implementations.
  • Inlining all or most styles : see @chriscoyier's recap. And if we chose only to use utility classes (see below), Tachyons could also be used this way, and could get along with the naming convention approach above - provided we avoid class naming collisions. Atomic CSS is probably the most explicit "inline-like" use of CSS classes.
    Some tools even implement their own syntax (compiled to CSS) to achieve more advanced layout features, like gridstylesheets.org's GSS (inspired by Constraint CSS and Apple's Visual Format Language).

Here's a notable example of a mixed approach : @TranscendMikey's Journey to Enjoyable, Maintainable Styling with React, ITCSS, and CSS-in-JS

Process

The gist of our interest in design systems and front-end frameworks here is how reusability is managed. What reusable (and modular) means is to allow the adaptation or customization ("theme" creation), extension (variation) and composition (mix components without breaking their "abstract" intent) of some generic, abstract pieces of CSS and their corresponding HTML. We've seen above a typical problem such an approach aims to address, and as there seems to be a general UNIX-inspired architectural trend in how different web technologies are used, an "ideal" process should be easily integrated with current organizations, allowing to produce useful (shared, open) components.

What I mean by (design) process is how to integrate and make such abstractions (or systems) as part of the front-end work - in other words : how patterns are identified, selected, how they apply or even why some new approach might be designed. The design process itself may also inform how decisions are actually made.
A side note on methodology : this is not just about clear thinking, as potential future algorithmic automation might as well make use of some degree of formalism in the design process itself.

Here's @yaili's flow chart on the process to submit a new pattern to Ubuntu's Vanilla framework :

Ubuntu's Vanilla framework new pattern flow chart

Of course, this can hardly ever be "formal" in the sense that it will require evaluating patterns - which already are abstractions of problems themselves - and wether it has value for reuse, e.g. worth the effort of making it generic at all.

TODO : include a few more analyses.

Architecture

I'm using the term architecture to refer to the conceptual level of organization, and file structure its concrete, physical implementation.

The organization of CSS may follow some categorization of styles. There is hardly one unique way of sorting out the styles for all imaginable projects out there, so this has to stay subjective (because a single generic architecture may not always be the most appropriate for projects of different size or nature).

However, following some logic helps in reducing time spent in making "structural" decisions (good architecture = less questions to ask) - here's an illustration of the Inverted Triangle - ITCSS metaphore :

ITCSS categories

  • Settings : global variables, colors, config switches...
  • Tools : default mixins and functions
  • Generic : ground-zero / root styles (resets, normalize.css, box-sizing...)
  • Base : unclassed html elements
  • Objects : cosmetic-free design patterns (OOCSS, agnostically named)
  • Components : designed components, chunks of UI (more explicitly named)
  • Utils or trumps : helpers and overrides, only affects one piece of the DOM (usually !important)

Looking into what makes an architecture "good", the goal of having less questions to ask can be illustrated in one of Steve Krug's Don't Make me think eloquent strip :

Steve Krug's illustration : Obvious VS Requires-Thought

Even though this particular illustration describes a hesitation about the naming of a button label, a similar "cost" would be incurred by an architecture (or any set of rules) that would be unclear, or not obvious to the developer. In other words, and especially in a composable, modular system, "we need to care about one another's scarce cognitive resources" - see Kathy Sierra (Serious Pony) keynote.
Among other factors, naming things is part of important architectural decisions, as it can contribute to speed up (or slow down) the understanding, communication, and ultimately, the adoption of a component, pattern, or organization in our case.

"Remember that no language (aside from a few exceptions) exists in isolation. By evolving and strengthening your design language, you have an opportunity to contribute to the larger language of the web, and to help make it more consistent and coherent for everyone." - excerpt from the Language of Modular Design.

In an attempt to simplify the general structure of the CSS codebase, and to cut down the time spent thinking about structural decisions (by trying to make things more obvious and predictable), another proposition of categorization is explained below.

File structure

Here are suggestions for organizing CSS files into 3 sub-folders (base, generic, and specific) - each of which can perfectly be further sub-divided into as many sub-categories as necessary. Both folder and file names can be considered categories.
The generic folder may also be used as an "incubator" of potential future abstract, reusable components - suggesting a component "maturation" process.

Alternative to discuss :
Only use 2 main categories, clearly indicating the intent : reusable or not.
See naming alternatives below.

Centralized, Single CSS folder

Current structure of the source code available as an example in this repository (comes from a Drupal 7 theme).

path/to/project/
    └── css/
        ├── base/           <- 1
        │   └── ...
        ├── generic/        <- 2
        │   └── ...
        ├── specific/       <- 3
        │   └── ...
        ├── node_modules/   <- (gitignored deps)
        │   └── ...
        ├── index.css       <- Input (compilation entry point)
        ├── main.css        <- Output (compiled result)
        └── critical.css    <- 4

Modular, Component-oriented structure

Similar to eCSS file organization (ch.5), inspired by @necolas's talk.

path/to/project/front-end-modules/
    ├── node_modules/       <- (gitignored deps)
    │   ├── sanitize.css/
    │   └── ...
    ├── src/
    │   ├── base/               <- 1
    │   │   ├── my-component/   <- *
    │   │   │   └── ...
    │   │   └── ...
    │   ├── generic/            <- 2
    │   │   ├── my-component/   <- *
    │   │   │   └── ...
    │   │   └── ...
    │   └── specific/           <- 3
    │       ├── my-component/   <- *
    │       │   └── ...
    │       └── ...
    ├── dist/
    │   ├── critical.css        <- 4
    │   ├── main.css            <- Output (compiled result)
    │   └── ...
    └── ...

* Individual Components

Our present goal is to progressively publish a growing library of open-source components, so that other projects can reuse these just like any other NPM package.

One of the main challenge is defining the "boundaries" between components, which can be very subjective - see the Component Boundaries and Shell/Content Pattern in the principles section above.
Here's some more advice from a javascript perspective : "don't overdo components - Your components should do something substantial. Does it have a non-trivial model ? Is it a combination of elements you use in lots of places ?"

Regarding file structure, take @benfrain's (eCSS) example :

shopping-cart-template/
    ├── shopping-cart.html
    ├── shopping-cart.css
    └── shopping-cart.js

Now here's another (work in progress) proposition, that would allow transpiling templates for maximum reusability (across various project stacks - e.g. Twig, Jade/Pug, etc.), planned standard web-components support, and integration into existing living styleguides techniques :

my-component/
    ├── src/
    │   ├── index.css
    │   ├── index.js
    │   ├── index.*                         <- source for transpiling into different tpl formats
    │   └── ...
    ├── dist/
    │   ├── my-component.css
    │   ├── my-component.js
    │   ├── my-component.html               <- standard-compliant Web Component (à la Polymer)
    │   └── tpl/                            <- transpiled template formats (e.g. for CMSes)
    │       ├── jsx/
    │       │   └── my-component.jsx
    │       ├── phptemplate/
    │       │   └── my-component.tpl.php
    │       ├── pug/
    │       │   └── my-component.pug
    │       ├── twig/
    │       │   └── my-component.html.twig
    │       └── ...
    ├── index.html                          <- demo, static HTML (e.g. for living styleguides)
    ├── README.md                           <- usage, building, contributing instructions, etc.
    ├── package.json
    └── ...

Transpiling templates aims at achieving some degree of interoperability (across different types of projects - e.g. Drupal, Wordpress, NodeJS) for these components. Automation that code generators like generator-suit provide could, for instance :

  • produce templates (and/or assets) directly inside current project sources (alternative : standalone or framework-specific "bridges", like @JohnAlbin's Component Libraries for Drupal 8)
  • avoid the need to compile from any single particular source format, by designing transpiler to accept different formats as input. Or, attempt to reuse existing standards (e.g. @mikaelsandin's use of XML, XSD and XSL, CSS modules in HTML / Pug (ex-Jade) templates CSS Modules integration...) ~ standard HTML5 could be considered (possibly using custom attributes for use by transpilers which could then be removed in compiled templates, and/or "guessing" variables ?)
  • handle backward-compatibility ?

Automation meme

About the wiring or bridging of data structure, variable names, objects ~ in other words, model → view correspondance (+ "hydration" ?) :
There seems to be some consensus in this discussion about Drupal's ongoing evolution of front-end implementations (Component-based rendering) around the concept of Presenter (from the MVP design pattern) - essentially, a transformation that prepares data for placement in markup (HTML) by correspondance (translation).
This is described in @wimleers's comment 51 as : "ensuring that components can be Angular, Ember, whatever components - that the inputs a presenter passes on to components are serializable (into JSON)" + comment 55 : "Make components non-Drupal-specific AKA implement what John Albin describes in 30".
→ @JohnAlbin's proposition to avoid tying the data variable name to the HTML display depends on a feature of Twig (embed, block / include ... with - which also exists in Pug/ex-Jade : extend, block / append) :

  • define a set of common variable names used in all reusable, abstract components (ex: modifier_class) - TODO : elaborate on Drupal's Paragraphs entities with the Shell / Content pattern to orient the standardization of a common language (e.g. exact same variable names across different technologies ?)
  • create an "intermediary" template handling the correspondance, essentially maintaining two separate groups of Twig files - a.k.a : the dual-Twig method - TODO : elaborate on Pug/ex-Jade equivalence ?

To take a bit further this promising lead, our "ideal solution" pursuit would also aim to be applicable in different contexts - i.e. architectures such as CALMM.js's ~ maintaining consistent state in the face of async inputs and "Immutable App" :

"Immutable App Architecture" schema, Lee Byron, Render 2016

See also https://github.com/jumpsuit/jumpsuit and https://www.reindex.io/blog/redux-and-relay/

About documentation, here are some guidelines for individual components, to include in index.html and/or README.md (in file structure above) :

  • A short summary the describes the purpose of the component (document the why)
  • Dependencies (e.g. to CSS, javascript, icons, images...) - note : dependencies to already packaged / released components would reside in package.json to be handled by NPM
  • Examples of how to use the component

TODO : list a few Living Styleguides tools and quick setup / getting started instructions here.
TODO : elaborate on approaches to extend components e.g. how to handle variation and combination.
TODO : variables, parameters, attributes : evaluate the feasability of abstracting templates.
TODO : mix in javascript components - look into recent developments in JS architecture, e.g. Immutable App Architecture - because they are fundamental structural choices about front-end implementations. Specifically, verify if the relationships between models and views have an impact on the decisions about the "boundaries" of CSS components (ideally, they shouldn't), and wether it matters at all (ideally, it shouldn't).

Terminology

  • modules : sometimes used to refer to individual components.
  • components : designate any reusable, modular, differenciable fragment or pattern of the interface.
  • (design) pattern : traditional software engineering principle, sometimes used for meaning UI design pattern.
  • UI design pattern : user interface components or interaction patterns.

Roadmap

Categorization

1. base/

Corresponds to :

  • SMACSS category : Base styles
  • ITCSS layers 1, 3, 4 :
    • Settings global variables, colors, config switches...
    • Generic ground-zero / root styles (resets, normalize.css, box-sizing...)
    • Base unclassed html elements
  • Atoms in @bradfrost's atomic design system terminology

Some base styles are likely specific to the current project, sucha as typographic settings, default tags appearance, colors, etc. - which is why it's been separated from the reusable, generic category (see below).
TODO : should we follow the "is reusable" logic, and move generic resets like normalize.css into the next category ?

File naming (sub-categories) :

HTML Periodic Table of elements

  • use categories from Josh Duck’s HTML Periodic Table (see screenshot pictured above) :
    • base/_root.css : html and :root (global) styles
    • base/_sections.css : sectionning
    • base/_grouping.css : grouping
    • base/_table.css : tabular data
    • base/_text.css : text-level semantics
    • base/_form.css : inputs and forms
    • base/_embedding.css : embedding content, media
    • base/_interactive.css : menu, details, command, summary tags
  • [optional] use double extension .vars.css for files containing "low-level", global variables (settings) - ex: base/_typography.vars.css, base/_colors.vars.css, base/_zindex.vars.css.

This point is between something generic (identical structure, e.g. files or variable names) and specific to individual projects (e.g. variable values).
See @nathanacurtis's Space in design systems for a "structural" kind of design convention (and potential naming system for variables).

Additional considerations :

2. generic/

Corresponds to :

  • SMACSS categories : Layout, Module, State
  • ITCSS layers 2, 5, 6, 7 :
    • Tools : default mixins and functions
    • Objects : cosmetic-free design patterns (OOCSS, agnostically named)
    • Components : designed components, chunks of UI (more explicitly named)
    • Utils or trumps : helpers and overrides, only affects one piece of the DOM (usually !important)
  • Molecules, Organisms in @bradfrost's atomic design system terminology

Styles with potential for reuse. Utlimately, these would end up forming third-party dependencies (e.g. packaged node_modules), but in this folder we may start work-in-progress components, which reusability could then be evaluated in several projects before deciding to make a release out of it.

Naming alternatives to discuss :

  • reusable/
  • abstract/
  • patterns/ (or ui-patterns/)

Examples of styles belonging in this category :

TODO : define groundwork ? e.g. some typographic design best practices & grid systems, accessible - universal - progressively enhanced design...
TODO : evaluate CSS Modules.

3. specific/

Corresponds to any of the generic/ category, plus :

  • SMACSS category : Theme
  • Templates and Pages in @bradfrost's atomic design system terminology

Low potential for reuse, but these styles shouldn't necessarily be unstructured either.

Naming alternatives to discuss :

  • project/
  • current/ (or current-project/)
  • not-reusable/

Within that folder, the organization should accomodate the size of the current project, and/or personal preference - ex : transposing @HugoGiraudel's Architecture for a Sass Project Examples :

  • Variables overrides (media queries values, objects and utilities customizations, etc.)
  • Variations, extensions of objects ("specialization" of generic styles)
  • Theme modifiers (as in @csswizardry's namespace terminology)
  • Custom components

4. critical.css

Experimental / to discuss : This file is the result of a separate compilation, taking any CSS file ending with .critical.css (double extension), and optionally (TODO : compilation options) folders like base/ or generic/ (as the main layout and typography are usually abstracted - like grids, widths, box-model measures or scales). Alternative tool to generate this file : @filamentgroup's criticalCSS More info :

CSS Coding Style and Naming Conventions

Linting

The choice not to enforce any particular selectors naming convention does not necessarily mean linting can't be done : it could test for obvious violations of the principles of the methodology (TODO : list a few implementations that could be combined without conflict).

It's worth noting that CSS Modules seem to have a similar stance : "For local class names camelCase naming is recommended, but not enforced."

Regression Testing

See @justin_tulloss's Building Accurate Visual Diffs.

Here are a few tools that got my attention over the last few years :

TODO : summarize quick setup instructions here.

About

Example of CSS code organization using CSS Next (PostCSS).

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published