Page MenuHomePhabricator

Consider CSS naming convention/methodology as coding guideline for Wikimedia projects
Open, Needs TriagePublic

Description

Our CSS naming conventions are currently relatively flexible, some might call it vague.
With two front-end lighthouse projects in the making, that both aim to adopt a (more) modular, componentized code approach:

we should consider adopting a CSS naming convention/methodology like BEM, SUITCSS, SMACSS or a derived mixture as coding guidelines in order to address growing complexity of our codebase and simplify navigation and organization of CSS rules.

BEM (Block Element Modifier)

Advantages:

  • Works well for components
    • Modular through independent blocks
    • Flexible as in being able to be configured fine-tailored to our needs
  • Has been widely popular in a number of
  • Why BEM provides a great overview of decisions behind structure and addresses common concerns

Disadvantages:

  • Grandchildren syntax problem https://assortment.io/posts/grandchild-elements-bem-css
  • Additional cognitive load when defining blocks, elements and modifiers.
    • (Opinion statement:) Code review and improved familiarity with the concept should help, also the cognitive load is on the implementor side, the code consumer, maintainer should have reduced load

Prior work

Related (prior) discussions:

Class naming horror variety across products

Wikimedia Foundation projects currently feature a wild and historically grown variety of CSS naming:

Concatenated words – editsection (great for internationalization), PascalCase – NavFrame, camelCase – firstHeading, kebap-case – mw-body-content, we got em all.

Examples

Current

<div id="siteNotice">
    <div id="centralNotice" class="cn-undefined">
    </div>
</div>

BEM

<div class="site-notice">
    <div class="site-notice__central site-notice__central--undefined">
    </div>
</div>

Acceptance criteria

  • Tasks for revising CSS in WVUI are made as needed.
  • T253671 is revised as needed.

Event Timeline

The Web team discussed BEM today. Here's the team's stance:

  • The team proposes that the WVUI library move forward with BEM as the initial naming scheme only. If a resolution of this task can be made prior to shipping WVUI, any renaming needed is far more practical. If naming cannot be concluded before shipping, this will add significant technical debt so participants are encouraged to see a timely resolution of T255100. The motivation for the interim naming is that: 1) we can't block development on these necessary but lengthy conversations, 2) we think BEM will work well, and, 3) and we don't want to invent new things needlessly. I will revise T253953 to mention the interim naming.
  • Once a decision has been made and this task is resolved, the convention chosen would also be wanted in Vector (T253671), elsewhere, and for the long term.

Content translation project started to use BEM in 2014 or so and later reduced its usage(did not enforce). This is not because of BEM's design, but because of our front end development practice and workflow:

  1. jquery and OOUI based frontend development require you to construct every individual component programmatically and write custom style for each of them. The hierarchy of DOM elements is difficult to comprehend in such source code. As the complexity grows, the disciplined way of building these DOM tree already get so complex. OOUI adds another layer of obfuscation because you inherit a DOM tree and then customize some of its child nodes by overriding some methods. The concept of "element", "modifier" become difficult to define in such inheritance path. Since OOUI already define a class list like this example: oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-buttonOptionWidget oo-ui-optionWidget-selected oo-ui-buttonElement-active it did not make any sense to apply BEM for the customized elements we build. In short, using BEM added more complexity to code, took more brain cycles from developer and gave nothing worthy in return.
  2. Related to the above point, I found code reviews becoming complex. Naming is already a subjective and complex. Debates about class names became common in code reviews. This is the fate of any coding conventions that cannot be validated/linted using development tools. So I stopped using BEM.

To illustrate the type of code review debates one can expect, let me give an example: According to BEM : "Modifier is A flag on a block or element. Use them to change appearance or behavior." From Wikimedia design style guide code, we see examples like .page--components, .btn--nav-main , .lnk--contrib( for github link) . This illustrates the issue of not able to clearly distinguish between qualifier, classification and modifier in some coding contexts. So developers will make a choice, and lead to code review debates. If BEM slows down development speed, it is not good sign.

However, I would support using BEM in Vue based development workflow, and we already use that in Vue components in Content translation.

  1. The Vue templates are very easy to comprehend and quickly gives you the picture of DOM tree you are going to develop. Makes naming very easy. Element states are data driven(reactive) and very clear for developers
  2. The Component based design is well suited for BEM style naming - We have an isolated DOM tree for a component, if everything in it is names like libraryname-componentname__element--modifier I found it very readable. Within each component, the nesting of DOM tree is also minimal(it should be so) so the confusion of element, sub element is less.
  3. Even though the Vue allows scoped styles, it has some caveats and better try to scope by naming patterns. BEM and LESS(or SASS/SCSS) helps there

From our recent experience, thanks to our evolving UI library, the need to customize css or writing styles in application code is nearly zero. So, for feature development, the importance of BEM is reduced. Unlike jquery or OOUI, we don't refer elements by class name in Javascript anyway, since it is declarative and reactive.

+1 for standardizing this and +1 for (a subdued version of) BEM.

Santhosh brings up a great point about code review: we don't want to waste time debating class names. As he said, it's somewhat inevitable, but the more we simplify our usage of BEM the less we'll have to do this. A couple of ideas on how we can simplify things:

  1. Using the "flattening grandchildren" method described in this article. In the past I've mostly done some combination of arbitrarily creating new blocks or adding sub-elements (.block__element__sub-element__why-it-this-class-so-long__help), which requires consideration during development and could be up for debate during code review. The component-based architecture of our new Vue projects is an ideal scenario for using the flattening grandchildren method since we don't need to worry as much about clarifying the markup structure of these components—it's already clear. See Suggestion.vue in MachineVision as an example: it's not really important to clarify via CSS class naming that mw-suggestion__label only appears inside mw-suggestion__content.
  2. Reserving modifiers for different states (e.g. --active, --destructive) rather than different types. For example, in WBMI we had various input types and used modifiers in the top-level class for each input component, e.g. class="wbmi-input wbmi-input--text", but wbmi-input wbmi-text-input might have been more appropriate since the input types were quite distinct and each had their own JS, template, and LESS files.

Regardless of the standard we eventually reach, I think our goals should include minimizing time spent thinking about class names and avoiding overly long class names.

I personally admire the BEM syntax, but I've seen developers struggle with it, especially around the grandchildren problem. Also, as Santhosh mentions, it adds mental overhead in an environment with different existing conventions.

The most common CSS pattern that I've seen during my time at the foundation is to have a top-level selector, and using Less nesting to essentially mirror the current DOM structure, e.g:

<div class="foo">
    <h3 class="foo-header">...</h3>
    <p class="foo-content">...</p>
</div>

and the corresponding Less like this:

.foo {
  .foo-header {
    ul {
       li {
           &:hover {...}
       }
    }
}

The issue I see with this pattern is that it leads to selectors that are overly specific and too coupled to the HTML structure.
BEM could in theory help with this, but even where we have used BEM, here in WVUI for example, we seem to stick to the habit of having a top-level selector and mirroring the HTML structure, and we end up with something that isn't really that different, just with more dashes, e.g:

.foo {
  .foo__header {
    .foo__header__list {
    ...
    }
  }
}

instead of the flatter structure that BEM could produce.

.foo { }
.foo-header {}
.foo-header__list {}

All that to say, I think BEM is great but it still has a learning curve and the potential to be misunderstood. Maybe if we add some linting rules around max-specificity, max-nesting-depth to encourage a flatter structure, that could help.