Lecture 8 More Foundational Concepts of CSS

Topics for today

The Cascade

CSS

The Cascade is an algorithm that defines how to combine property values originating from different sources and how to resolve conflicts.

Components of the Cascade

Related: Inheritance, that we will cover next week

Importance


			body {
				background: yellow;
			}
		

			body {
				background: pink !important;
			}
		

Origin


			button {
				border: none;
			}
		

			button {
				border: 2px outset #777;
			}
		
- The second tier of the Cascade looks at where the rule was defined. We have already overridden many User Agent styles and didn't think much of it. In this case, the cascade follows expectations: author stylesheets (i.e. from websites) generally have higher priority than User Agent (UA) stylesheets (i.e. from the browser). - This is by design: UA styles are merely a fallback for pages that have not defined any CSS. - There is also a third origin: User, for stylesheets defined by the website *visitor*, i.e. the browser user, which sits right between them. Originally, CSS was envisioned as a way for web users to also customize the websites they visit, and browsers used to provide this functionality out of the box. Unfortunately, it did not pan out, and browsers removed the UI for this functionality. It can still be accessed through extensions (e.g. [Stylus](https://chrome.google.com/webstore/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne?hl=en))

Origin


			button {
				border: none;
			}
		

			button {
				border: 2px outset #777 !important;
			}
		
Fun fact: The hierarchy here is actually reversed for `!important` rules, meaning that an `!important` browser default rule wins over an !important website rule, i.e. you can never override it no matter what you try.

Any author styles
on a given element
override any UA styles

(Unless !important is involved)

  • What happens when there are conflicting declarations about the same element?
  • Here it’s quite obvious, since the selector is the same, so last one wins.
- But what about here? - Surprised at the result? The reason is that `#container *` has higher *specificity* than `div.box`.

Selector specificity is a heuristic for inferring importance from querying logic

Calculating specificity

  • CSS resolves conflicts via selector specificity, not source order.
  • Specificity is calculated as follows:
    1. A = number of ids in the selector
    2. B = number of classes, attribute selectors, pseudo-classes
    3. C = number of tag selectors
    4. Specificities are compared by comparing the three components in order: the specificity with a larger A value is bigger; if the two A values are tied, then the specificity with a larger B value is bigger; if the two B values are also tied, then the specificity with a larger C value is bigger; if all the values are tied, the two specificities are equal.
  • If specificity is the same, then the conflict is resolved based on source order.

Specificity: Special cases

Selector Specificity
style="background: red" (∞, ∞, ∞)
:not(em, strong#foo) (1, 0, 1)
:is(em, #foo) (1, 0, 0)
:where(em, #foo) (0, 0, 0)
- Declarations within the `style` attribute have infinite specificity - Pseudo-classes that take selectors as arguments (e.g. `:not()`, `:is()`) do not count like regular pseudo-classes - `:where()` has zero specificity regardless of its argument

🤺 Specificity battle! 🤺

>
Sometimes, specificity "predicts" which selector is more specific accurately, but other times it doesn't. Take a look at these two selectors. The first one includes all `<div>` elements on the page *except* the two with `id="foo"` or `id="bar"`. The second one selects all `<div>` elements with `class="box"` that are descendants of `<main id="container">`. Which one seems more specific to you?

Selector specificity is a heuristic for inferring importance from querying logic
So what do we do when it’s wrong?

Adjusting specificity

Increasing specificity

  • Repetition: .foo.foo.foo
  • Negation: :not(#nonexistent)

Decreasing specificity

  • #foo to [id="foo"]
  • :where()

Inheritance

  • Notice how the p, em, mark elements have the same font family, font size, and color that we set on their ancestor, body
  • What happens if we add a border to the body? Does it get inherited too?

Some properties are inherited, and some aren’t. Why? It's a heuristic.

What kinds of properties inherit?

background font border text-decoration display color padding white-space margin text-shadow box-shadow box-sizing outline --*

Inherited Not inherited

Components of the Cascade

Where does inheritance fit in?

Inherited properties have lower precedence than *any* values directly applying to the element, regardless of origin, specificity, or order.

Q: Where does inheritance sit in the cascade?
A: The very bottom

inherit is an explicit value that makes any property inherit

Shorthands & Longhands

Shorthands & Longhands

A shorthand is a CSS property that represents multiple other properties, its longhands, or constituent properties. Setting a shorthand is equvalent to setting all of its longhands.

border: .3em dotted steelblue;

Disambiguation Shorthands often accept their arguments in either order, but only iff *disambiguation* is possible.

border: dotted .3em steelblue;

border: steelblue .3em dotted;

Longhands can be shorthands too

Different shorthands can resolve to the same longhands

What happens if we set both the shorthand and some of its longhands?
What happens if we set the same properties in a different order? Do you understand why we got the result we did?

Often they evolve over time

CSS 1 CSS 2.1 CSS Backgrounds & Borders Level 3 CSS Backgrounds & Borders Level 4

Naming is not always consistent

What happens if we swap the order of these two declarations? Why??

⚠️

A shorthand always sets all of its longhands, whether values have been provided for them or not

Any value not explicitly provided is set to `initial`.

padding: .2em .5em .1em .5em;

Top
Right
Bottom
Left
🕐

background: url(foo.png) 50% 50% / 100% auto no-repeat gold;

Some shorthands have a mixed syntax: their longhands can be mostly specified in any order, but some values need to come after other values, for disambiguation. In this case, `background-size` can only follow `background-position` (separated from it with a slash)

background: gold no-repeat 50% 50% / 100% auto url(foo.png);

font: 120%/1.5 Helvetica Neue, sans-serif;

Some shorthands require at least some values to be specified to be valid. Here, [`font`](https://developer.mozilla.org/en-US/docs/Web/CSS/font) requires at least a value for `font-size` and `font-family`.

Shorthand usability


			background: url(cat.jpg)
			            0 10% / 100% 100%
			            no-repeat
			            content-box border-box
			            fixed;
		

			background-image: url(cat.jpg);
			background-position: 0 10%;
			background-size: 100% 100%;
			background-repeat: no-repeat;
			background-origin: content-box;
			background-clip: border-box;
			background-attachment: fixed;
		
Computer languages are also interfaces, they are just text-based interfaces. The same usability dimensions apply. When it comes to CSS shorthands and longhands, which of these is more learnable? Which of these is more efficient?

Don’t
Repeat
Yourself

DRY Principle

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system

The Pragmatic Programmer, Andy Hunt and Dave Thomas

A big part of writing good code is avoidingcodeknowledgeduplication


			def right_triangle_perimeter(a, b, c):
				return a + b + c

			# ... later on
			p = right_triangle_perimeter(3, 4, 5)
		
  • Here, you can see a contrived example of WET Python code. c is dependent on a and b, so providing it is superfluous.
  • Not only is this an example of poor efficiency (both to write and to read), but also poor safety, since we may inadvertently provide arguments that are inconsistent.

			from math import sqrt

			def right_triangle_perimeter(a, b):
				return a + b + sqrt(a**2 + b**2)

			# ... later on
			p = right_triangle_perimeter(3, 4)
		

Keeping CSS DRY

Avoid presentational class names

Presentational class


				.red-button {
					background: hsl(0, 80%, 90%);
				}
			

Semantic class


				.primary-button {
					background: hsl(0, 80%, 90%);
				}
			

Keeping CSS DRY

Use font-relative units


				button {
					border-radius: 5px;
					padding: 5px 12px 6px;
					font-size: 24px;
					line-height: 24px;
				}
				button.large { font-size: 46px; }
			

				button {
					border-radius: .2em;
					padding: .2em .5em .25em;
					font-size: 100%;
					line-height: 1;
				}
				button.large { font-size: 200%; }
			

Keeping CSS DRY

Use shorthands wisely

Hardcover
Paperback
Audiobook

				.tab {
					border-radius: .3em .3em 0 0;
					padding: .1em .5em .1em .5em;
					margin: 0 .1em 0 .1em;
				}
			
Hardcover
Paperback
Audiobook

				.tab {
					border-radius: .3em;
					border-bottom-left-radius: 0;
					border-bottom-right-radius: 0;
					padding: .1em .5em;
					margin: 0 .1em;
				}
			
Note that the DRY version has more code. Short code is not the goal, maintainability is.

Overgeneralization

Rule of three Three strikes and you refactor

Duplication is far cheaper than the wrong abstraction

Sandy Metz

CSS Variables / Custom properties

  • CSS variables (aka Custom Properties) start with -- and behave like normal CSS properties.
  • We read their value via the var() function and we can use that anywhere (including inside calc()), except inside url().
  • They are dynamic, and can be set in the inline style or even via JavaScript!
  • They help reduce repetition, and thus make code more maintainable. They also help encapsulation: Other people can use your components and style them via the variables you have exposed, without having to know and override the internal structure of your CSS.
  • Notice the repetition here: We have to repeat the button color 3 times! And changing it requires repeating so much code. How can CSS variables help us reduce this repetition and create themes more easily?