The Art of CSS
(upbeat music) - Thanks, John.
Okay.
As we know, CSS is a simple programming language. However, it is not an easy one.
The syntax itself you can learn in 15 minutes or at least at the very least you can look it up if you need to, if you can't remember what particular things do. But the semantics of CSS is the hot pot.
So this talk, I will be talking about the cascade, inheritance and custom properties.
And see how we might be able to organise our CSS in a way that makes it easier for us, and also more semantic for the future versions of ourselves and for our colleagues.
So before we deep dive into this, a little bit of a glossary reminder so that everyone knows what I'm talking about. So on the screen, you should see a CSS declaration block.
In green you have the selector and inside the selector block, you have multiple property declarations.
And they're all in the form of a property and a value pair. So what is the cascade? The property values of an element can be defined by multiple declaration blocks.
Each of those blocks can have a different specificity. The more specific it is, the higher the precedence of it.
If you happen to have two blocks with the same specificity, the source order applies.
Where the last one have higher precedence.
So those are the basic rules of the cascade. Let's look at what the different levels of specificities that there might be.
Specificity level one is where we operate on the HTML elements and pseudo-elements.
These obviously carries semantic value in the HTML. Things like your headers, your paragraphs, your attributes, sorry your angles, lists items and all of that. They carry information in the HTML and they can carry information in the CSS as well. If all we did with our CSS was using these ones, we end up with a low fidelity look-and-feel. We can further augment the CSS with level 10 cascade levels. So these refer to your classes.
Pseudo-classes with things like, whether a pattern is hovered over or a link, whether it's visited or unvisited.
As well as attributes.
In particular, ARIA attributes, increase sematic richness of your HTML quite significantly. We can style on these and end up with a medium fidelity look-and-feel of your website. Further to that, we have the cascade level 100s. These are your IDs.
These unfortunately no semantic value outside of your application.
Div ID app, for example, it's completely meaningless to something outside of the application.
But we can style on this.
And if you did so you end up with a high fidelity look-and-feel in your website. Finally, there's CSS level 1000.
These are inline styles.
You could annotate your HTML with inline lifestyles. Now these carry no semantic value.
But it does allow us to complete the look-and-feel of our document.
The thing with the cascade is interference. We could end up with a scenario where the properties cascade constructively from multiple declaration blocks.
For example, on the left we have a paragraph which is a specificity level one, specifying a particular font-style.
We have class foo with specificity 10, specifying a different property.
ID bar specifying yet another property.
And on the right, we have a snippet of HTML, which also applies an inline style.
So we have a paragraph, we have a class and ID and a style. And in this case, because they do not specify the same thing, you end up with constructive interference in the resulting style.
You could also have destructive interference. I'm using the same examples here.
We have our paragraph, our class foo, our ID bar and our inline style.
All of them specifying the same property.
And in this case, we end up with destructive interference where the most specific or the one with the highest specificity dominates over everything else.
And we end up with tomato text in this particular example. Now I'm not saying that constructive and destructive interference, one is better than the other.
In fact, we often need to make use of both constructive and destructive interference.
In this example here, I have all paragraphs coloured red. But then a paragraph that follows another paragraph, have a different background and colour.
So on the right hand side, you can see that the first paragraph only have red texts. The second paragraph has a green background, which is a constructive interference.
As well as a blue text which is a destructive interference. So what I wanna stress or repeat, is that interference can be good if they're constructive but it can also be good if they are destructive. So neither of them is good nor bad.
Now, in addition to the cascade rules, we also have rules that come from where the style sheet originate from.
With increasing precedents, we have the user agent style sheets.
These provide a reasonable look-and-feel.
So user agent is like your browser.
You try opening an HTML file with no associated CSS, you end up with a reasonable look-and-feel to the HTML document.
The next level is a user style sheet.
Now, not many people are aware that browsers actually are capable of allowing the users, the humans, to insert in their custom settings. So the stylish plugin is an example of the browser plugin that allows you to specify your user settings. The next origin that has higher precedence is the author of the websites themselves.
And then we have author bang important.
If you ever come across a CSS file with bang importance, what's happening is that the author of that CSS is basically trying to escape off the origin into the author bang origin style sheet, to give it higher precedence.
But let's not forget that users can specify their own bang importance as well.
This means that users have the ultimate control in how they want their websites to look like. For example, they might include ad blockers in there. Or they might remove annoying hitters, footers and anything else on their page because they own that CSS at that point.
Finally, theoretically, there's a user agent bang important. But I believe these don't actually exist out in the world. Otherwise there's really no point in having the style sheet at all.
Okay, so that's the CSS cascade.
Let's talk now about the inheritance.
HTML or the DOM inherently has a hierarchical structure. So therefore inheritance is just part of what it is. An element can inherit undefined styles from it's ancestors. Things like colour inherit by default.
So if you have a series of nested divs, for example, the outermost div can say, this is the colour. And all the inner divs will just say, well, I'll just use what my parents using.
And you have inheritance by default.
There are some properties like display that do not inherit by default.
So it makes no sense for a child element to inherit the display flex of his parent.
So they don't inherit by default.
When we write CSS, typically what we're doing is we're changing the inheritance rules.
So what we normally do, we say, here's a new value and you shall use it from here onwards.
But we can also force that inheritance to say, please inherit this if it's not already inherited. Or please inherit this, if perhaps a lowest specificity block set to apply one rule and we say, no, we don't want that rule, we just wanna use the inherit rule.
You could also have initial, which it says, follow the CSS specifications or unset, which is the hybrid of the two, inherit and initial properties.
Let's talk briefly about single responsibility principle. As JavaScript developers, we apply this way we can everywhere.
It's a good thing, right? We have a function, a method that only ever does one thing. What does it mean in the context of CSS? I believe what it means is that our declaration box should be readable on it's own.
And it's easy to reason without looking at the render output.
Now, if you take this into the inheritance point of view, basically what we can have is, we can build this single responsibility based on hierarchical roles.
Let's start on the right hand side.
You can see that I have a div with multiple classes attached to it.
One of the classes, as child, basically admits that I I'm a child of some other parent.
And in doing so, I shall do things that are relevant to me as a child, position.
Top, left, bottom, right, all of those things. They are relevant because I'm a child of some other element. I could also apply the responsibility of myself. Things that I care about, right? My colour, my background, my borders, all of those things are for myself.
I may be a parent of some of the elements that can display flags, align items, justify content, all of those things.
That's me being a parent to my children.
I could also be a peer of something else.
I might say, I need this margin if I'm next to you. So if we break down our CSS this way, making use of the hierarchy as a tool to split the responsibility of our CSS, we can read our CSS and say, I know what this block is doing, without looking at the render output.
And our HTML, well, basically what ends up happening is that we're basically composing our styles. Now, if we think about this a little bit further, CSS has the facility to describe hierarchy as well. So in this example here on the right, I have a parent element, class foo.
And I have children elements within it.
I do not have to explicitly annotate my children attributes because in my CSS I've actually described the relationship. I've got foo.
And I can see that these are the properties that are relevant to it being a parent.
And I can see foo, star, which basically says these are the children of foo and they have particular properties that are relevant to it.
This leads me to a side conversation, which is CSS and HTML are co-dependent documents. We can interchangeably shift responsibility between the CSS, HTML.
And we cannot write one without thinking about the other. Or rather we should not write one without thinking about the other.
Here's some examples.
We can in theory, push all the complexity into CSS. So you can see here on the left hand side, I have a complicated selector, so div P.
I also have a complicated declaration block with all sorts of noise.
But if you look at the HTML, there's nothing there at all. This may be useful if, for example, our source material came from markdown where there is no facility to insert CSS classes. Or we can go the other direction and say, we want our CSS to be as simple as possible. The example you have on the screen now is from atomic CSS, but tailwind CSS follows the same idea.
The idea is every CSS class does one thing and one thing only.
And on the right hand side, you basically have all the complexities shifted into your HTML.
And you're very, very verbose with your HTML. In the case of atomic CSS, you don't actually write the CSS. The Pisa rule will look at your HTML and say, okay, these are the CSS that you need.
And it creates a CSS file for you.
What both of these slides show you is that you can shift the complexity in CSS or in HTML and still end up with the same result.
So the trick therefore, and the art is finding a balance that works.
If I rewind a few slides back, you can see that this example that I showed you earlier, where I made use of the CSS hierarchy to present something, I end up with CSS that is very, very nice and simple. I can understand it.
I can visualise what it does, without opening my browser. And I've got an HTML that is concise.
It doesn't have an overloaded amount of CSS classes, everywhere.
Finally with the last few minutes I've got left, let's talk about custom properties.
Custom properties are defined just like any other property. They obey the same cascade rules.
So specificity one, 10, 100 and 1000, they follow the same rules.
They are inherited by default.
And you have this nice thing where you can have an optional fallback.
And I'll talk about that in a second.
For example, I have here a data-driven styling from the HTML. So on the first paragraph on the right, we see a paragraph with class foo.
If you look at the CSS, we can say, okay, all right. I've got colour.
If foo colour is not defined, it falls back to tomato. So I've got a tomato text in my first paragraph. In my second paragraph, I've got foo and foo modified. Foo modified is specificity 10.
In fact, all the classes are of specificity 10. So here we have colour is defined by foo colour. And foo colour is firebrick.
So we end up with a firebrick text in our second paragraph. In our third paragraph, we have ID instead of a class. So we're basically using specificity 100 to achieve an orchid colour text.
In the last paragraph on the right, we have an inline specification of the custom property with a SeaGreen text.
And basically we're using specificity 1000 to produce our desired style.
This last example looks like inline styles and you go, what's the point? The benefit of CSS properties comes when we have properties that depend on other properties. When this happens, the value is computed once per element. And it is computed value that gets inherited by all of it's children.
Let's look at the example.
In the first paragraph I've got class foo.
If you look at the CSS definition of foo, we've got foo background, that depends on foo angle. But foo angle is unspecified at that particular point in time.
So what ends up happening is we have none.
We don't have a particular background.
It defaults to unset.
But in the other later paragraphs, I've got foo angles, specify inline, 10 degrees, 20 degrees and 30 degrees. So in that scenario, I basically end up with a gradient of those particular angles in there.
So my CSS custom properties is becoming a little bit like a function.
It allows me to specify the parameters, foo angle. And then the entire style is created based on that one property.
And of course in browsers where this is not supported, there's a graceful fallback to unset.
So when we talk about, the art of CSS, really we're talking about finding a balance between a global first approach versus a local first approach.
Now CSS the way it's been designed is meant for designing a consistent design system. Everything is global first and then we decrease the scope while increase the specificity as required. But as divs, we tend to go the other way around and focus on the components first, resulting in per-component styles.
Local first and increase scope only as required. But we have to remember that CSS and HTML are co-dependent documents and they should be co-authored. So what we wanna do now is this shift the responsibility accordingly between global first and local first ideas to increase the overall semantic-ness of our HTML and our CSS.
So in summary, I've talked about the cascade.
I've talked about levels one, 10, 100 and 1000. I would encourage you to try to embrace the cascade and not try and run away from it.
Apply to the common case and then apply to the exceptions. I've spoken about the DOM hierarchy.
I talk about parent-child relationships and sibling relationships.
I encourage you to think about this and describe that inheritance structure in your CSS. And you find that you will significantly reduce the verbiage in your HTML.
This allows you to use composition of roles to build the styles that you want.
These roles, as I've described it, are usually hierarchical, but they don't have to be. And if you do this, you find that again, your CSS and your HTML are much more readable. Finally, custom properties is a lot more than just a replacement for Sass variables.
They are dynamic and they can be used for data-driven documents. The next time you want to create a bar chart, instead of pulling a graph library, how about trying to do it using just divs and CSS custom properties.
(soft music) These are my contact details or rather my company's contact details.
So contact us.
(upbeat music)