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
Selectors
The Cascade
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?
divspanpemolulstrongarticleabbrdetails
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!
- 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
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 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?
It was on the same line → inline.
It had a width → block
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.
Theory: An element whose representation is outside the scope of CSS
Practice: flows around surrounding content like an inline-block, contents are not affected by the current document's styles
Only replaced elements can have intrinsic dimensions or an intrinsic aspect ratio (but not all do).
Examples
Block elements
divh1h2h3h4h5h6pbodyhtml
Inline elements
aspanstrongemmarkcodeciteabbr
Inline-block elements
buttoninputtextareaselect
Replaced elements
imgvideoaudioiframecanvasoptionobjectembed
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.
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
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.
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)
html
head
meta
title
link
body
header
h1
ul
li
li
li
li
li
li
img
li
li
li
li
li
strong
li
li
li
li
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
- [`: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
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?
Specificity: Scoring function to calculate which selector is more specific
Order: All else being equal, last one wins
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
Specificity is a tuple with three elements, (A, B, C)
A = number of #id selectors
B = number of .classes, :pseudo-classes, [attributes]
C = number of element (tag) selectors
Compare specificities by interpreting the three numbers as digits in a sufficiently large base.
E.g. (1, 0, 0) > (0, 100000000, 100000000)
CSS resolves conflicts via selector specificity, not source order.
B = number of classes, attribute selectors, pseudo-classes
C = number of tag selectors
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