CSS Inheritance, The Cascade And Global Scope: Your New Old Worst Best Friends

CSS Inheritance, The Cascade And Global Scope: Your New Old Worst Best Friends

 

As luck would have it, there is already a technology, called CSS, which is designed specifically to solve this problem. Using CSS, I can propagate styles that cross the borders of my HTML components, ensuring a consistent design with minimal effort. This is largely thanks to two key CSS features:
  • inheritance,
  • the cascade (the “C” in CSS).
Despite these features enabling a DRY, efficient way to style web documents and despite them being the very reason CSS exists, they have fallen remarkably out of favor. From CSS methodologies such as BEM and Atomic CSS through to programmatically encapsulated CSS modules, many are doing their best to sidestep or otherwise suppress these features. This gives developers more control over their CSS, but only an autocratic sort of control based on frequent intervention.
I’m going to revisit inheritance, the cascade and scope here with respect to modular interface design. I aim to show you how to leverage these features so that your CSS code becomes more concise and self-regulating, and your interface more easily extensible.

Inheritance And font-family Link

Despite protestations by many, CSS does not only provide a global scope. If it did, everything would look exactly the same. Instead, CSS has a global scope and a local scope. Just as in JavaScript, the local scope has access to the parent and global scope. In CSS, this facilitates inheritance.
For instance, if I apply a font-family declaration to the root (read: global) html element, I can ensure that this rule applies to all ancestor elements within the document (with a few exceptions, to be addressed in the next section).
html { 
  font-family: sans-serif;
}

/* 
This rule is not needed ↷
p { 
  font-family: sans-serif;
}
*/
Just like in JavaScript, if I declare something within the local scope, it is not available to the global — or, indeed, any ancestral — scope, but it is available to the child scope (elements within p). In the next example, the line-height of 1.5 is not adopted by the html element. However, the a element inside the p does respect the line-height value.
html {
  font-family: sans-serif;
}

p {
  line-height: 1.5;
}

/* 
This rule is not needed ↷
p a {
  line-height: 1.5;
}
*/
The great thing about inheritance is that you can establish the basis for a consistent visual design with very little code. And these styles will even apply to HTML you have yet to write. Talk about future-proof!

The Alternative Link

There are other ways to apply common styles, of course. For example, I could create a .sans-serif class…
.sans-serif {
  font-family: sans-serif;
}
… and apply it to any element that I feel should have that style:
<p class="sans-serif">Lorem ipsum.</p>
This affords me some control: I can pick and choose exactly which elements take this style and which don’t.

Any opportunity for control is seductive, but there are clear issues. Not only do I have to manually apply the class to any element that should take it (which means knowing what the class is to begin with), but in this case I’ve effectively forgone the possibility of supporting dynamic content: Neither WYSIWYG editors nor Markdown parsers provide sans-serif classes to arbitrary p elements by default.

That class="sans-serif" is not such a distant relative of style="font-family: sans-serif" — except that the former means adding code to both the style sheet and the HTML. Using inheritance, we can do less of one and none of the other. Instead of writing out classes for each font style, we can just apply any we want to the html element in one declaration:
html {
  font-size: 125%;
  font-family: sans-serif;
  line-height: 1.5;
  color: #222;
} 
 
 

The inherit Keyword Link

Some types of properties are not inherited by default, and some elements do not inherit some properties. But you can use [property name]: inherit to force inheritance in some cases.

For example, the input element doesn’t inherit any of the font properties in the previous example. Nor does textarea. In order to make sure all elements inherit these properties from the global scope, I can use the universal selector and the inherit keyword. This way, I get the most mileage from inheritance.

* { font-family: inherit; line-height: inherit; color: inherit; } html { font-size: 125%; font-family: sans-serif; line-height: 1.5; color: #222; } Note that I’ve omitted font-size. I don’t want font-size to be inherited directly because it would override user-agent styles for heading elements, the small element and others. This way, I save a line of code and can defer to user-agent styles if I should want.
Another property I would not want to inherit is font-style: I don’t want to unset the italicization of ems just to code it back in again. That would be wasted work and result in more code than I need.

Now, everything either inherits or is forced to inherit the font styles I want them to. We’ve gone a long way to propagating a consistent brand, project-wide, with just two declaration blocks. From this point onwards, no developer has to even think about font-family, line-height or color while constructing components, unless they are making exceptions. This is where the cascade comes in.

Exceptions-Based Styling Link

I’ll probably want my main heading to adopt the same font-family, color and possibly line-height. That’s taken care of using inheritance. But I’ll want its font-size to differ. Because the user agent already provides an enlarged font-size for h1 elements (and it will be relative to the 125% base font size I’ve set), it’s possible I don’t need to do anything here.
However, should I want to tweak the font size of any element, I can. I take advantage of the global scope and only tweak what I need to in the local scope.

* { font-family: inherit; line-height: inherit; color: inherit; } html { font-size: 125%; font-family: sans-serif; line-height: 1.5; color: #222; } h1 { font-size: 3rem; }
 
If the styles of CSS elements were encapsulated by default, this would not be possible: I’d have to add all of the font styles to h1 explicitly. Alternatively, I could divide my styles up into separate classes and apply each to the h1 as a space-separated value:

<h1 class="Ff(sans) Fs(3) Lh(1point5) C(darkGrey)">Hello World</h1>
 
Either way, it’s more work and a styled h1 would be the only outcome. Using the cascade, I’ve styled most elements the way I want them, with h1 just as a special case, just in one regard. The cascade works as a filter, meaning styles are only ever stated where they add something new.

Element Styles Link

We’ve made a good start, but to really leverage the cascade, we should be styling as many common elements as possible. Why? Because our compound components will be made of individual HTML elements, and a screen-reader-accessible interface makes the most of semantic markup.
To put it another way, the style of “atoms” that make up your interface “molecules” (to use atomic design terminology) should be largely addressable using element selectors. Element selectors are low in specificity, so they won’t override any class-based styles you might incorporate later.
The first thing you should do is style all of the elements that you know you’re going to need:
a {} p {} h1, h2, h3 {} input, textarea {} /* etc */ The next part is crucial if you want a consistent interface without redundancy: Each time you come to creating a new component, if it introduces new elements, style those new elements with element selectors. Now is not the time to introduce restrictive, high-specificity selectors. Nor is there any need to compose a class. Semantic elements are what they are.
For example, if I’ve yet to style button elements (as in the previous example) and my new component incorporates a button element, this is my opportunity to style button elements for the entire interface.
button { padding: 0.75em; background: #008; color: #fff; } button:focus { outline: 0.25em solid #dd0; } Now, when you come to write a new component that also happens to incorporate buttons, that’s one less thing to worry about. You’re not rewriting the same CSS under a different namespace, and there’s no class name to remember or write either. CSS should always aim to be this effortless and efficient — it’s designed for it.
Using element selectors has three main advantages:
  • The resulting HTML is less verbose (no redundant classes).
  • The resulting style sheet is less verbose (styles are shared between components, not rewritten per component).
  • The resulting styled interface is based on semantic HTML.
The use of classes to exclusively provide styles is often defended as a “separation of concerns.” This is to misunderstand the W3C’s separation of concerns principle. The objective is to describe structure with HTML and style with CSS. Because classes are designated exclusively for styling purposes and they appear within the markup, you are technically breaking with separation wherever they’re used. You have to change the nature of the structure to elicit the style.
Wherever you don’t rely on presentational markup (classes, inline styles), your CSS is compatible with generic structural and semantic conventions. This makes it trivial to extend content and functionality without it also becoming a styling task. It also makes your CSS more reusable across different projects where conventional semantic structures are employed (but where CSS ‘methodologies’ may differ).

Special Cases Link

Before anyone accuses me of being simplistic, I’m aware that not all buttons in your interface are going to do the same thing. I’m also aware that buttons that do different things should probably look different in some way.
But that’s not to say we need to defer to classes, inheritance or the cascade. To make buttons found in one interface look fundamentally dissimilar is to confound your users. For the sake of accessibility and consistency, most buttons only need to differ in appearance by label.
<button>create</button> <button>edit</button> <button>delete</button> Remember that style is not the only visual differentiator. Content also differentiates visually — and in a way that is much less ambiguous. You’re literally spelling out what different things are for.
There are fewer instances than you might imagine where using style alone to differentiate content is necessary or appropriate. Usually, style differences should be supplemental, such as a red background or a pictographic icon accompanying a textual label. The presence of textual labels are of particular utility to those using voice-activation software: Saying “red button” or “button with cross icon” is not likely to elicit recognition by the software.
I’ll cover the topic of adding nuances to otherwise similar looking elements in the “Utility Classes” section to follow.

Attributes Link

Semantic HTML isn’t just about elements. Attributes define types, properties and states. These too are important for accessibility, so they need to be in the HTML where applicable. And because they’re in the HTML, they provide additional opportunities for styling hooks.
For example, the input element takes a type attribute, should you want to take advantage of it, and also attributes such as aria-invalid to describe state.
input, textarea { border: 2px solid; padding: 0.5rem; } [aria-invalid] { border-color: #c00; padding-right: 1.5rem; background: url(images/cross.svg) no-repeat center 0.5em; } A few things to note here:
  • I don’t need to set color, font-family or line-height here because these are inherited from html, thanks to my use of the inherit keyword. If I want to change the main font-family used application-wide, I only need to edit the one declaration in the html block.
  • The border color is linked to color, so it too inherits the global color. All I need to declare is the border’s width and style.
  • The [aria-invalid] attribute selector is unqualified. This means it has better reach (it can be used with both my input and textarea selectors) and it has minimal specificity. Simple attribute selectors have the same specificity as classes. Using them unqualified means that any classes written further down the cascade will override them as intended.
The BEM methodology would solve this by applying a modifier class, such as input--invalid. But considering that the invalid state should only apply where it is communicated accessibly, input--invalid is necessarily redundant. In other words, the aria-invalid attribute has to be there, so what’s the point of the class?
 

Comments

Popular posts from this blog

Mobile Website Design Services - GS Web Technologies

WordPress Development Services in Chandigarh | India

Infographic: 2021 forecast on internet users in India - GS Web Technologies