body
and html
elements to provide high-level scopes for CSS on a site but I’ve run into issues with specificity in my CSS. After thinking about the problem for a while and reading the thoughts from various CSS luminaries (like Harry Roberts) I came up with a solution that I’m quite happy with.Usage Details
html
Classes
html
classes usually are generally the same across the site and include things like:
- JavaScript availability
- font loading status
- Modernizr detects
body
Classes
body
classes usually include modifiers for page-specific features or designs such as:
- the name or type of the page
- if the page has a sidebar
- if the page needs to hide horizontal overflow
The Setup
In the past, I’ve always simply put a class on the html
or body
element and used it in CSS with the element and the class:
html.js .c-expanding-content__body {
// Use JS to control expanding content display when JS is available
display: none
}
I prefer having the element name present in the selector because it eliminates ambiguity about where this particular modifier is coming from.
The Problems
This leads to two different problems.
The Linter Problem
I have used scsslint to lint my Sass for a couple years now and it’s been a great tool for enforcing code consistency. However, it’s rules for QualifyingElement are not element-specific. This means that I need to occasionally disable linters via source comments in the places where I’m using an html
or body
class.
This has been annoying and had a tendency to grate at me as an indication that something really wasn’t right. My original solution was to eventually submit a pull-request to the project to support specying a list of elements that it was OK to use with other selectors; but then I started running into The Specificity Problem.
The Specificity Problem
html.js .c-expanding-content__body {
// Use JS to control expanding content display when JS is available
display: none
}
.js__expanded .c-expanding-content__body {
display: block;
}
Since I have the html
tag in the selector to know when “js is available”, the specificity of that selector is higher than the operational selecter just below. I’ve worked around it in the past by simply duplicating a class (.js__expanded.js__expanded .c-expanding-content__body
) but that was pretty hackish.
There had to be a better way.
The Solution
Requirements
- keep the indication that the particular class is coming from the
html
tag or thebody
tag for clarity - don’t use the actual tag in the selector to prevent specificity issues
Implementation
I’m a big fan of BEM and I use it in every project. I started thinking about how the BEM practices could be applied in this case and all of a sudden the answer struck me. Instead of html.js
I could just use .html--js
.
Every html
or body
class just gets the tag name as the “block” and the informational bit as the “modifier”. Occasionally I’m actually dealing with “sub-items” and those are easily handled via the “element” (e.g. .html__font-loaded--primary
and .html__font-loaded--secondary
).
Note, I’ve considered prefixing with some kind of global namespace like g-
but haven’t found a strong need for it yet and am only just starting to incorporate CSS namespaces into my processes.
Implementation Notes
There are a few places where I’ve implemented this behavior with third party tools/solutions and can provide some implementation notes.
Modernizr
If you are generating your Modernizr from a Grunt task, it is easy to specify the classPrefix
.
modernizr: {
dist: {
dest: "js/libs/modernizr.min.js",
crawl: false,
parseFiles: false,
tests: [
'svg',
'flexbox',
'inlinesvg'
],
options: [
'setClasses'
],
uglify: true,
classPrefix: 'html--'
}
}
WordPress
Similarly you can hook into WordPress to ensure that your body
classes are named as you want.
function theme_prefix_body_classes($classes) {
$func = function($class_name) {
if ( 'body--' !== substr($class_name, 0, 6) ) {
$class_name = 'body--' . $class_name;
}
return $class_name;
};
return array_map($func, $classes);
}
add_filter( 'body_class', theme_prefix_body_classes, 20 ); // Note "increased" priority
The Conclusion
So far I’ve used this approach on a couple projects and am very pleased with the result. The CSS is cleaner and causes fewer exceptions to work around but continues to provide the clarity that I want.