Lecture 6 Foundational Concepts of CSS

CSS beyond the browser?

Web technologies can also be used to make apps that can be installed on a phone! [Progressive Web Apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) is a set of technologies enabling this, and at the bare minimum, it requires a [manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest) (just a JSON file with metadata about the app) and a `<link rel="manifest" href="manifest.json" />` in the page’s `<head>`.

Size of CSS: ~1.6K “words”

Topics for today

Document flow & the box model

In CSS, every element is a box

A lot of CSS is about how these boxes are painted, and how they affect other boxes

Lorem Ipsum dolor sit amet
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor
Lorem Ipsum
ad minim veniam
ullamco laboris
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
velit esse cillum dolore
consectetur adipiscing elit, sed do eiusmod tempor incididunt
Excepteur sint occaecat cupidatat non proident

Block elements display: block;

  • Laid out top to bottom*
  • Line break before and after
  • Box as wide as available space (extrinsic)
  • Box as tall as all contents (wrapped)
  • Text wrapping inside box
Lorem Ipsum dolor sit amet
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Inline elements display: inline;

  • Laid out left to right*
  • Inline with surrounding text
  • Box as wide as all contents (intrinsic)
  • Box as tall as one line
  • Text wrapping by box fragmentation
velit esse cillum dolore
consectetur adipiscing elit
sed do eiusmod tempor incididunt
Excepteur sint occaecat cupidatat non proident
\* In Western languages. Which elements that we have seen already are block and which inline?

Which of the elements we have seen so far are block and which inline?

div span p em ol ul strong article abbr details

Block Inline

Note that many of the elements we have seen are missing from this list. That’s because block and inline are not the whole story.
The display property How does the browser know which elements are block and which are inline? It’s not magic, that is also just CSS that is being applied to elements by default, called the *User Agent Stylesheet*. Open this in a new tab and inspect the paragraphs, `<em>` and `<strong>` elements, or even the `<body>`, `<head>` and `<html>` elements. We can set the `display` property ourselves as well, to override the defaults. Let's make the paragraphs inline and the `<em>` and `<strong>` elements block!

Each property has an initial value

display: initial; → display: inline;
font-weight: initial; → font-weight: normal;
- We can set a property to its initial value by setting it to the keyword [`initial`](https://developer.mozilla.org/en-US/docs/Web/CSS/initial), which is valid for every property (it is a *global keyword*) - How do we find out what a property’s initial value actually is? By looking up its definition. E.g. here’s [`display`](https://developer.mozilla.org/en-US/docs/Web/CSS/display#formal_definition) - Usually the initial value is the value that produces no effect, e.g. [`font-weight: normal`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#formal_definition)

Browsers apply default CSS through their User Agent Stylesheet


			html {
				display: block;
			}

			body {
				display: block;
				margin: 8px;
			}

			article,
			aside,
			details,
			div,
			dt,
			figcaption,
			footer,
			form,
			header,
			hgroup,
			html,
			main,
			nav,
			section,
			summary {
				display: block;
			}

			head, link, meta,
			script, style, title {
				display: none;
			}
		
That is just a CSS file that is applied to every website. Curious what that looks like? Here are the UA stylesheets of some popular browsers: - [Chrome](https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/html/resources/html.css;bpv=0) - [Firefox](https://github.com/mozilla/gecko-dev/blob/master/layout/style/res/html.css) - [Safari](https://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css)

The combination of initial values and UA stylesheet values are the defaults we start with

Any CSS we write has priority over these defaults. We will discuss the exact mechanism in the next lecture.
The Box Model

There are a few properties that are crucial to how these boxes are sized:

  • padding controls the spacing from the element’s content to its edge. margin specifies the spacing from the element’s edge to the elements around it, and can be negative to bring the element closer to other elements instead of farther. border allows you to specify a visible border. It’s placed outside the padding.
  • Padding & border widths are added to width and height, not subtracted from it.
  • Outlines, shadows etc do not affect the box in any way.
  • Always use sufficient padding, otherwise the text is uncomfortable to read. As you may remember from the Graphic Design lecture, you usually need more padding horizontally than vertically.

Switching box model


				textarea {
					padding: 1em;
					width: 100%;
				}
				­
			

				textarea {
					padding: 1em;
					width: 100%;
					box-sizing: border-box;
				}
			
Switching box models Sometimes the default box model is a problem. Let's look at an example. We want this textarea to have a width of 100%. However, if we set `width: 100%` it doesn’t work the way we expect. What is happening? How can we fix it? We *can* fix it with `calc(100% - 1em)` but that way we are repeating the padding and border twice. How can we improve our code? Let’s apply `box-sizing` to make our code more maintainable.
Alternate Box Model
  • What happens when we specify a width of 100%? Does it match our intent?
  • We can change that with box-sizing: border-box, but that has its issues too
  • Each element has a content box, a padding box, a border box and a margin box that are delimited by the corresponding areas.
  • You can use the browser developer tools to inspect the box model, via the "Computed" tab in the Elements panel.
Box properties on inline element The properties we saw behave entirely differently on inline elements! - `width` and `height` have no effect - `padding` and borders are applied to each fragment individually, and does not move it in any way. - Horizontal `margin` applies spacing before and after the element, vertical margin does nothing.

Block or inline?

<input>: Block or inline?

WUT?! 🤔🤔🤯

Block elements

  • Laid out top to bottom*
  • Line break before and after
  • Box as wide as all available space
  • Box as tall as all contents (wrapped)
  • Text wrapping inside box
  • We can set width, height, margin
Lorem Ipsum dolor sit amet
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Inline elements

  • Laid out left to right*
  • Inline with surrounding text
  • Box as wide as all contents
  • Box as tall as one line
  • Text wrapping by box fragmentation
  • width, height, margin have no effect.
velit esse cillum dolore
consectetur adipiscing elit
sed do eiusmod tempor incididunt
Excepteur sint occaecat cupidatat non proident

Inline-block elements

  • Laid out top to bottom*
  • Line break before and after
  • Box as wide as all available space
  • Box as tall as all contents (wrapped)
  • Text wrapping inside box
  • We can set width, height, margin-top, margin-bottom
  • Laid out left to right*
  • Inline with surrounding text
  • Box as wide as all contents
  • Box as tall as one line
  • Text wrapping by box fragmentation
  • width, height, margin-top, margin-bottom have no effect.
velit esse cillum dolore
Lorem Ipsum dolor sit amet
consectetur adipiscing elit
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
sed do eiusmod tempor incididunt
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Excepteur sint occaecat cupidatat non proident

Block, inline, or inline block?

  • Is image a block or inline element? Or maybe inline-block?
  • We can set width, height, margin, although they are not entirely independent
  • If we inspect, it's inline (?!?) What's going on?
  • Tip: object-fit and object-position help us adjust how the object contained within the replaced element should be positioned.

Replaced elements

Examples

Block elements

div h1 h2 h3 h4 h5 h6 p body html

Inline elements

a span strong em mark code cite abbr

Inline-block elements

button input textarea select

Replaced elements

img video audio iframe canvas option object embed
  • Even elements like head or style are boxes too!
  • They just have display: none from the User Agent stylesheet, which hides the element and removes it from normal flow.
  • Same with elements with the hidden attribute
  • This is a design principle of the web platform: as much as possible, its behavior must be explainable through its own primitives, not special "magic"
  • You can also apply display: none to elements you want to hide.

Other useful layout modes

Flexbox One dimension
Grid Two dimensions
Block, inline, and inline block were the first layout modes, and were designed to support **document layout**. Though block and inline do play a huge role in today's layouts, these days we also have other layout modes that support creating UIs much better. We will explore them in more detail in the Layout lecture.

Selectors

h1 { font-size: 300%; line-height: 1; }
  • The selector tells the browser which elements the rule is about, in this case h1 headings.
* { margin: 0; padding: 0; box-sizing: border-box; }
  • The universal selector allows us to apply certain declarations to every element in the page.
  • It can be useful for undoing certain user agent styles, but also really dangerous. E.g. * {font-weight: normal} might seem like a reasonable default, but it means our strong elements also will start from the same non-bold baseline.

Simple Selectors

Most Common simple selectors * element .class #id
Attribute selectors [attr] [attr="value"] [attr^="starts"] [attr$="ends"] [attr*="anywhere"] [attr~="anywhere"]
Pseudo-classes (sample) :hover :focus :checked :target :nth-child() :invalid
  • These are called simple selectors because they represent a single selection criterion.
  • Here, they are listed by frequency of use: the selectors that are less frequently used are more transparent.
  • More frequency of usage data for selectors

Compound selectors

input[type="text"]
=
input AND [type="text"]

Later in this lecture we will learn more ways to apply logical operators to CSS selector queries.

Concatenation = AND

img [loading=lazy] img[loading=lazy]
<img src="cat.jpg" alt="">
<img src="cat.jpg" alt="" loading="lazy">
<iframe src="widget.html" loading="lazy">

Concatenating simple selectors intersects them

Concatenating simple selectors intersects them (i.e. it is equivalent to an AND operation). The result is called a Compound Selector E.g. a.docs matches an <a> element with the class docs. Note that element selectors can only be first in the sequence (e.g. [title]element is invalid)

Pseudo-classes

button:hover { background: greenyellow; }
  • Pseudo-classes allow us to style states of an element, like an imaginary class that is added and removed automatically.
  • Does it remind you of JS? Indeed, a lot of pseudo-classes are inspired from functionality that used to require JS. However, using pseudo-classes is a lot easier than writing JS because the browser keeps track of when to apply and unapply the state for you.

Pseudo-classes are simple selectors that allow us to style states of an element.

- Here we have a button that does not react at all when we interact with it. Furthrermore, it is not clear that the second one is disabled. - Which dimensions of usability suffer? - Let’s use pseudo-classes to improve the experience. - Here we can see some example pseudo-classes in action: - `:enabled` and `:disabled` (what's the advantage over just using a `[disabled]` attribute selector?) - `:hover` matches when the pointer is over the element - `:active` matches when the pointer is pressed on the element - `:hover` and `:active` behave differently on mobile - `:focus` matches when the element is focused, iff it can receive focus - Note that by chaining simple selectors together we are intersecting them. We will learn more about this later today.
Here is a different style for this list. We want to eliminate the line on the last item. What can we do?

Pseudo-classes can be dynamic (activity-based) or structural (DOM tree-based)

There are also logical pseudo-classes, and we will cover them later today.

Pseudo-classes

Dynamic

  • Mouse over element :hover
  • Mouse down on element :active
  • Currently focused :focus
  • Contains currently focused :focus-within
  • Checked radio or checkbox :checked
  • Elements targeted by the url #hash :target

Structural

  • first child :first-child
  • last child :last-child
  • only child :only-child
  • odd children :nth-child(odd)
  • every 4th paragraph after the 8th from the end p:nth-last-of-type(4n + 8)
  • Structural pseudo-classes match elements based on their position among their siblings in the tree. Here we explore the :*-child family of pseudo-classes.
  • :first-child and :last-child match elements who are first and last among their siblings, respectively.
  • :only-child is equivalent to :first-child:last-child
  • :nth-child() is a generalization. It accepts an argument of the form An+B (e.g. 2n+1) and matches elements whose position among their siblings matches the argument for some non-negative integer n. A and B can be negative.
    • :nth-child(even) is equivalent to :nth-child(2n) and matches each 2nd, 4th, 6th etc child.
    • :nth-child(odd) is equivalent to :nth-child(2n+1) and matches each 1st, 3rd, 5th etc child.
    • :nth-child(3n+1) matches every 3rd child starting from the first.
    • :nth-child(1) is equivalent to :first-child
    • Activity: Select the first 4 items. Now select items 2-5!
  • :nth-last-child() is exactly the same as :nth-child() but starts counting from the end.
    • :nth-last-child(1) is equivalent to :last-child
    • Activity: Select the first item, only when the total count of its siblings is ≥3
  • There is also a :*-of-type family of pseudo-classes, with exactly the same syntax, that only counts elements of the same type. You can experiment with it here

Combinators

We have now added a nested ordered list, and our rule seems to be applying to that too. What can we do?

Combinators allow us to match on relationships between elements

:checked + label

Next sibling combinator

label directly after a or

label that contains a or

label directly after or

h1 ~ h2

Subsequent Sibling combinator


					<h1>…</h1>
					<h2>…</h2>
				

					<h1>…</h1>
					<p>
					<h2>…</h2>
				

					<h1>…</h1>
					<section>
						<h2>…</h2>
					</section>
				

					<h2>…</h2>
					<h1>…</h1>
				

Combinators cannot look backwards
but :has() can!

  • Images inside paragraphs: p img
  • <h2>s right after <h1>s: h1 + h2
  • Paragraphs that contain images: p:has(img)
  • <h1>s that come before <h2>s: h1:has(+ h2)
- [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) is fairly new, and until recently there was *no way* to do the kinds of things in the last two examples

Compound selectors + combinators
=
complex selectors

Terminology recap

Term Meaning Example
Simple selector Single criteria .foo
Compound selector Intersection of simple selectors .foo.bar
Complex selector Multiple Compound selectors ul.bar li
Selector list Multiple comma separated complex selectors ul.bar li, .foo

Logical operations

CSS selectors express sets of HTML elements

How to perform set operations? (¬, ∪, ∩)

Logical operations we've seen

  • AND: p.warning = p.warning
  • OR: td, th = tdth
  • NOT via overrides:
    a { text-decoration: none }
    a:hover { text-decoration: underline }
We've already seen some ways to perform logical operations. And for many years in CSS' history, these were the only ways. But they are quite limited. How do we express AND or OR between complex selectors? And how do we negate without having to explicitly set both states?

Negation pseudo-class: :not()

With overrides


					a {
						text-decoration: none;
					}

					a:hover {
						text-decoration: underline;
					}
				

With negation


					a:not(:hover) {
						text-decoration: none;
					}
				
The negation pseudo-class allows us to express our intent more clearly. It also means we can negate without having to set both states.

AND/OR between complex selectors:
The :is() pseudo-class


				:is(h1, h2, h3, h4, h5, h6):is(section *, article *) {
					border-bottom: .1em solid slategray;
				}
			
Note that this particular selector can be rewritten more succicntly as `:is(section, article) :is(h1, h2, h3, h4, h5, h6)`

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()

Topics for next week