« resources

Smashing Workshop

Cascading Styles

a value for every property

workshops.oddbird.net/smash24/cascade/

Cmd/Ctr-K for Controls

An ordered list (cascade) of style sheets.

— Håkon Lie, Cascading HTML style sheets – a proposal

  • We’ve talked about the ordered list
  • or cascade of style sheets
  • And the way this…
css…
button          { background: gray; }
.action         { background: darkBlue; }
[type=“submit”] { background: darkGreen;
                  background: var(--submit, black); }
#send           { background: maroon; }
💥 Conflicts!
  • Inevitably leads to conflicts
  • And often, as developers,
  • We might think of those as being selector conflicts
  • But from the browser perspective,
  • It’s the individual declarations that we’re looking at
  • The selector is a bit of metadata attached to that declaration

For Browsers Every CSS Property
On Every HTML Element,
Must Have a Single Value

  • Before a browser can render the page
  • Every CSS property
  • on every HTML element
  • must have a single value

596 Distinct Property Names

* including Editor’s Draft proposals

w3c List of CSS Properties
  • There’s 500-some properties
  • And every one of them needs a value
  • In order to render the element

Every <button> Needs One Background-Color
& One Text Color
& One Border-Top-Left-Radius
& …

  • Every button needs
  • one text color,
  • one border-top-left-radius,
  • and so on

The Cascade (Process) Resolves Conflicts

removes extra values

  • What we call ‘the cascade’ now
  • is a browser process, an algorithm
  • merging our list of stylesheets
  • (‘cascading’ them together)
  • and resolving any conflicts between declarations
  • That process ensures we don’t have extra values

Inheritance… Provides Dynamic Defaults

fills in missing values

  • And then inheritance helps
  • Fill in some of the missing values
  • We’re not going to set all 500-some properties on every element!
  • So good defaults are essential,
  • (we’ve already seen some of that)
  • But there’s a whole separate step for it…
  1. Filtering
  2. Cascading (includes specificity)
  3. Defaulting (includes inheritance)
  4. Resolving
  5. Formatting
  6. Constraining
Value Processing (or ‘value resolution’)
  • After the cascade has finished.
  • Both are steps in a larger process called value resolution
  • Or value processing…
  1. Filtering (0+ declared values)
  2. Cascading (0|1 cascaded value)
  3. Defaulting (1 specified value)
  4. Resolving (1 computed value)
  5. Formatting (1 used value)
  6. Constraining (1 actual value)
  • Where each step gets us closer
  • to that actual rendered style on the page
  • Filtering to get all the relevant declarations
  • for a specific property on a specific element,
  • Cascading to resolve any conflicts between those declarations
  • Defaulting to fill in anything that wasn’t declared explicitly
  • And then several steps that help us resolve contextual & automatic values

Cascade & Inheritance Internal Browser Algorithms

  • On the one hand,
  • This is all internal logic, right?
  • Just an algorithm that browsers use…
Tina Turner as Aunty Entity in the Mad Max Thunderdome, with the law 'Two styles enter, one style leaves' in bold text
Necessary Browser Algorithms
  • to thunder-dome our declarations
  • And find a winner
The Crab Nebula photo: Webb Space Telescope
Description of Web Reality
  • Almost like the laws of physics…
  • A way of describing the universe around us,
  • The way the world works.
  • That’s all true, but also…
Yoda reaching out to control the force, surrounded by jungle swamp
Expressive Authoring Tools
  • tools provided to us, intentionally
  • part of the language,
  • designed for us to communicate…

Express… Purpose & Priority

  • At each step we get tools
  • to help us express purpose and priority
  • Of our hints and suggestions.
  1. Filtering

  2. Cascading
  3. Defaulting
  4. Resolving
  5. Formatting
  6. Constraining
Get a list of declared values (possibly empty)
  • That starts with filtering our stylesheets
  • To get all the relevant declarations
  • for a given property on a specific element

A Relevant Declaration

Cascading & Inheritance: Filtering

(Read it from the specification)

  1. In a stylesheet that applies to this document
    • (media attr)
  2. Not in a false conditional rule
    • (at-rules - media, supports, preferences, etc)
  3. In a selector that matches the HTML Element
  4. Is syntactically valid
Resilient HTML [present, debug]
  • Invalid CSS (like HTML) is discarded/ignored
  • Called ‘parse time’ validation
  • Not everything can be validated at this point
  • Useful, because we catch issues early
  • Before we discard other declarations
css…
.short { border: thin double; }
.long {
  border-top: thin double;
  border-left: thin double;
  border-bottom: thin double;
  border-right: thin double;
  border-image: none;
}
Expand Shorthands
  • This is also when we expand shorthand properties
  • Elements don’t have a border property
  • They have four border properties, and a border-image
  • Border is shorthand for setting all of them
css…
.short { border: thin double; }
.long {
  /* declared */
  border-top-width: thin;
  border-top-style: double;

  /* reset to 'initial' value */
  border-top-color: currentColor;
  border-image: none;
}
  • Four sides are also shorthand, including
  • The width, style, and color of each
  • Shorthands also reset the un-specified values
  • Initial value of border-color is currentColor
  • (We’ve set that implicitly)
  • Also border-image which is reset-only here

To start over Use Shorthand Properties

  • Shorthands are great for starting over
  • Reset everything
  • Build from the ground up
  • But they’re risky if…

To adjust details Use Longhand Properties

  • You just want to make small adjustments
  • Long-hand properties are more precise
  • And leave everything else intact
  1. Filtering (list of declared values)
  2. Cascading

  3. Defaulting
  4. Resolving
  5. Formatting
  6. Constraining
Get a single cascaded value (possibly empty)
  • Once we have our list of declared values for a property
  • We know what conflicts exists
  • And we can use the cascade to resolve those conflicts
Mechanical coin sorter on a table, with a ramp at the top and a small-to-large series of slots
  • Cascade works like a coin sorter
  • Instead of measuring the size of a coin
  • Measuring the priority of each declaration
  • See which ones make it farthest
Same coin sorter, with all slots crossed off except for the largest one with coins in it, and an arrow pointing at those coins that says 'move on'
  • All we care about is the coins that made it the furthest
  • We can ignore any empty columns
  • And discard coins that didn’t make it quite as far
  • Those are gone from memory now!
  • But we still don’t have a single winner
Mechanical coin sorter duplicated multiple times in semi-transparent overlays
  • So we find another coin sorter, and we do it again
  • Over and over, until we have a single coin
  • Each time comparing different features
  • Not just size, but shape, width, metal, whatever
  • Ok, but not coins -
css…
property: value;
  • We’re talking about declarations here…
The Cascade (as a funnel) [present, debug]
  • And there are seven ‘coin sorters’ that we’ll use
  • 7 Questions we can ask to compare and discard declarations
  • Until we have a single winner
  • Starting with origins & importance, which we’ve discussed
  • Who requested it, and how much do they care?
  • And we’ve talked about the final step,
  • Order of appearance, which was declared ‘last’ in the code?
  • Along the way, we also ask:
    1. Does it come from the shadow DOM? (context)
    2. Is it an inline style? (element attached)
    3. What layer is it in? (cascade layers)
    4. How specific is the selector? (specificity!)
    5. (maybe) How close is the scope root? (proximity, new)

❗Important Styles Always Win

  • Importance could be split out at the top
  • Important styles always win
  • Over normal, non-important styles
We're Not Alone, with browser logos, a guy in construction clothes carrying a laptop, and ET
  • But that would miss the point
  • This is about collaboration
  • And importance is here to help us…
Kylo Ren and Rey
standing in front of an entire star war
with spaceships and lasers
and AT-AT walkers
  • Balance the power of the various origins…
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 🎨 Author Styles
  4. ❗🎨 Author Important
  5. ❗👥 User Important
  6. ❗🖥 User Agent Important
  • So we can override user defaults
  • But users can also protect the defaults
  • That are most important to them
  • We’ll see this importance-reversal behavior again
  • in other steps of the cascade
  • But technically there are several other origins…

For Intermediate Styles

in transitions and animations

  • Officially two
  • For ‘intermediate’ or interpolated styles
  • Generated by animations & transitions

hotPink » teal

  • Take two colors (HotPink to Teal)
  • If we want to move between them,
  • The browser has to go through other colors…

hotPink
rgb(93 138 220) ?!
teal

  • (Depending on the color space we use)
  • Maybe we go through this bluish color
  • Along the way, that has to _override_both hotPink and teal
css…
button {
  background: teal;
  transition: background 1s;
}
button:hover { background: hotPink !important; }
  • For transitions,
  • The start and end values might come from any origin,
  • With any importance
  • The transition isn’t applied until after the cascade
  • So we’ve all had our say
  • We agree on the end points,
  • And we agree on transitioning between them
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 🎨 Author Styles
  4. ❗🎨 Author Important
  5. ❗👥 User Important
  6. ❗🖥 User Agent Important
  7. ➡️ Transitions
  • So the intermediate values have to override everything
  • Transitions are the most powerful ‘cascade origin’
css…
:target { animation: bg-change 3s ease-in both; }
@keyframes bg-change {
  from { background: pink; }
  to { background: cyan; }
}
  • Animations are a little different
  • Only the animation property is in the cascade
  • But keyframe declarations live outside the cascade
No !important keyframes [present, debug]
  • Importance isn’t allowed in keyframes
  • will cause the declaration to be ignored
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 🎨 Author Styles
  4. 🏇🏽 Animations
  5. ❗🎨 Author Important
  6. ❗👥 User Important
  7. ❗🖥 User Agent Important
  8. ➡️ Transitions
  • So we still need a way to override specific parts of an animation
  • By slotting animated values just above the normal origins
  • Important styles become immune animation

👍🏼 Transition ❗Important Styles

👎🏼 Animate ❗Important Styles

Just to restate:

  • We can transition between important styles
  • And those transitional values will override everything else
  • But we cannot use keyframe animations to change important styles
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 💭 Non-CSS Presentational Hints
  4. 🎨 Author Styles
  5. 🏇🏽 Animations
  6. ❗🎨 Author Important
  7. ❗👥 User Important
  8. ❗🖥 User Agent Important
  9. ➡️ Transitions
  • And then there’s one… pseudo-origin?
  • For non-CSS presentational hints
html…
<img src="..." width="160px" height="90px" hidden>
<s>strikethrough</s>
  • This includes presentational HTML attributes like
  • width, or height, or hidden
  • Or the default styles of the strikethrough element
  • Since it’s a presentational element, not a semantic one
  • They’re a little weird, and in-between,
  • so spec doesn’t list them as their own origin,
  • But it does treat them like one
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 💭 Non-CSS Presentational Hints
  4. 🎨 Author Styles
  5. 🏇🏽 Animations
  6. ❗🎨 Author Important
  7. ❗👥 User Important
  8. ❗🖥 User Agent Important
  9. ➡️ Transitions
  • Those non-CSS styles slot in between
  • User and authors styles
  • There are no ‘important’ presentational hints.
  • Although sometimes…
css…
[hidden] { display: none !important; }
  • There should be one
  • Hidden deserves to be important
  • And we can make it important in our CSS
  • I include this as part of a modern reset in my projects
Spock, and Spock with a goatee
  • Important styles always win,
  • But they also split us into two paths
  • An important mirror universe…
The Cascade (as a funnel) [present, debug]
  • We’ve seen that impact origins, reversing their priority
  • But they have a similar effect on several other steps as well
  • Including the next one,’

🌗 (Shadow) Context

styling web components

  • Which is called Context in the cascade
  • It’s like a sub-‘origin’, but for shadow DOM styles
  • Styles that come from the shadow DOM
css…
/* shadow DOM styles */
:host(my-element) { outline: thin solid red; }
[part=title] { outline: thin dashed red; }
::slotted(span) { outline: thin dotted red; }

/* light DOM styles */
my-element { outline: thick solid green; }
my-element::part(title) { outline: thick dashed green; }
my-element > span { outline: thick dotted green; }
  • Since we generally can’t select Shadow DOM elements
  • From outside the shadow DOM,
  • There are only a few places where these conflicts can happen
  • Primarily light-DOM host element, exposed parts, and slotted elements
  • I’m not an expert with web components so I might have missed something
  • But those are the primary cases I know about

Shadow Styles Are Custom Defaults

similar to user agent origin

  • We can think of shadow DOM styles
  • As similar to browser defaults, from the user agent origin
  • We’re defining ‘custom elements’ and we can give them ‘custom defaults’
The Cascade (as a funnel) [present, debug]
  • Normal author styles from the page
  • will override those default shadow DOM styles
  • Unless we add !important to protect essential styles,
  • and then (again) the priority is reversed
  • Shadow styles can ‘take back’ priority when necessary

Again… Importance is Defensive

and reverses priority

  • Again, importance is defensive
  • Allowing component authors to protect essential styles
Important Shadow Context [present, debug]

(demo if needed)

The Cascade (as a funnel) [present, debug]
  • Element attached (or inline) styles are next
  • They’re an exception to our importance reversal
  • They work the same on both tracks…
html…
<button style="
  background: mediumVioletRed; /* winner! */
  color: white !important; /* winner! */
  border: thin solid;
"></button>
css…
button {
  background: white;
  color: teal !important;
  border: thick dashed !important; /* winner! */
}
Element-Attached Styles
  • Important styles can be used to override inline styles
  • But when both styles are normal, or both important
  • Then inline styles always win
The Cascade (as a funnel) [present, debug]
  • Remember, each of these steps is resolved
  • Before we move on to the next step
  • So if a single declaration wins after comparing
  • Origin, importance, context, or element attachment,
  • Then we never bother with layers or specificity and so on.

Cascade Layers

Cascading & Inheritance Level 5
  • Cascade Layers are both new
  • Proposed at the end of 2019, but also…
Support data from CanIUse.com on the css-cascade-layers feature
There’s also a polyfill [Can I Use]
  • Well supported across browsers, for almost 3 years now
css…
@layer settings {}
@layer tools {}
@layer generic {}
@layer elements {}
@layer objects {}
@layer components {}
@layer overrides {}
  • They give us a way to organize our code
  • And help us manage the cascade
  • Or maybe more to the point…

Layers of Specificity

  • They can help us manage specificity
  • Because they come before specificity in the cascade
  • So the declaration in the highest layer wins
  • Before we bother to compare specificity
  • Inside the winning layer
Quick Layers Live Demo [present, debug]

demo:

  • Source order is fragile, layers are explicit
  • Specificity only matters inside a layer
  • Un-layered styles win by default
  • Layers stack in order
  • Define the order up front

@layer { … }

  • A layer rule starts with at-layer
  • And then brackets
  • We can put any CSS inside those brackets…

@layer <name> { … }

Not initial, inherit, unset, revert, revert-layer, etc
  • And we can optionally give our layers a name
  • Almost any name we want
  • Avoiding a few reserved names
css…
@layer reset {
  audio[controls] { display: block; }
  [hidden] { display: none !important; }
}
  • And we can put almost anything
  • inside each layer block

@import url(…) layer;
@import url(…) layer(<name>);

  • We can also import entire stylesheets into a layer,
  • Using the keyword (for an anonymous layer)
  • Or the layer function if we want to add a name
css…
@layer reset { /* least powerful */ }
@layer default { /* … */ }
@layer theme { /* … */ }
@layer components { /* more powerful */ }
/* unlayered styles: most powerful */
Stack in the order they’re introduced
  • Layers stack in the order they are introduced
  • The first layer has the lowest priority,
  • Building up to the last layer,
  • And then un-layered get highest priority
css…
@layer reset { /* least powerful */ }
@layer components { /* more powerful */ }
@layer reset { /* still least powerful */ }
  • The great thing about naming layers
  • Is that we can reference them again
  • And add more styles to them, from anywhere in our document
  • The ordering is based on when each layer was first introduced
  • So it’s also useful…

@layer <name>, <name>, <etc>;

Explicit Layer Order
  • To define an explicit layer order up-front
  • There’s a shorthand syntax for that
  • We can provide a list of names
  • Right at the top of our CSS file
html…
<style>/* keep this before linked styles */
@layer reset, framework, components, utilities;
</style>
<link rel="stylesheet" href="">
<link rel="stylesheet" href="">
Can live in the HTML / template
  • Or even in our HTML,
  • Before we import other stylesheets
  • There’s a lot more we can do here,
  • but we’ll come back to it when we talk about CSS architecture
  • and organizing conventions
  • The key for today is that…

Like Origins, ❗️important Layers Reverse

  • Like origins and context
  • Important layers are reversed
  1. Resets (weakest)
  2. Themes
  3. Components
  4. un-layered (strongest)
  • So if we have three layers
  • Each one overriding the previous

!important

  • And then we add important styles
  • Inside each layer
  1. Resets
  2. Themes
  3. Components
  4. un-layered
  5. ❗important un-layered
  6. ❗important Components
  7. ❗important Themes
  8. ❗important Resets
  • We now have six layer
  • With the important layers reversed
Kylo Ren and Rey
standing in front of an entire star war
with spaceships and lasers
and AT-AT walkers
  • Again, the goal is balance

Protect Styles From Future Layers

Prioritize -> Layers
Protect -> Importance

  • If we just want to override
  • Or manage priorities
  • That’s what layers are for
  • Importance only when essential
css…
@layer reset {
  [hidden] { display: none !important; }
}
  • So when I set the hidden attribute to display none
  • And add importance,
  • I can put that in my lowest layer,
  • I’ll call it the reset layer
  • And future, more powerful layers
  • won’t be able to override this declaration
  • It’s important, and so it’s protected.
  1. Resets
  2. Themes
  3. Components
  4. un-layered
  5. Inline Styles
  6. ❗important un-layered
  7. ❗important Components
  8. ❗important Themes
  9. ❗important Resets
  10. ❗important Inline Styles
  • While we’ve already covered inline styles,
  • And they aren’t technically ‘layers’ in the same way
  • It can be useful to understand how these two features of the cascade
  • Weave together somewhat,
  • With inline styles at the top of both
  • The normal and important layer order
The Cascade (as a funnel) [present, debug]
  • Which brings us to the most discussed
  • Maybe the most feared and hated part of the cascade…

🎯 Selector Specificity

Selectors (Level 4)
  • Selector specificity
  • This is where we compare
  • the selectors used for each declaration
css…
* { /* universal */ }
p { /* type */ }
.summary { /* attribute */ }
#call-to-action { /* id */ }
Simple Selectors
  • All simple selectors fall into four ‘categories’
  • Each with increasing priority in cascade…
css…
* { /* universal */ }
The universal (star) selector
  • The universal (star) selector
  • Alone in its category…
OddBird.net with red outlines around every element on the page
Similar to initial values - generic and universal
  • Selects all HTML elements
  • (but not pseudo-elements like before or after)
  • We can use this for extremely generic settings
  • Across every element on the page
css…
button, div, span {
  /* 'type' (element) */ }
::before, ::after, ::part() {
  /* 'pseudo-element' */ }
Type (element) selectors
  • Element & pseudo-elements
  • Are called ‘type’ selectors
OddBird.net with red outlines around every link on the page
Similar to browser defaults - provide a baseline
  • They are much more targeted, but still fairly broad
  • And (until recently) not something we could control
  • HTML defines what elements we have available to use
  • But still, these selectors can us establish
  • More customized defaults,
  • Similar to user agent styles
css…
.action, .summary {
  /* 'class' */ }
:hover, :user-invalid {
  /* 'pseudo-class' */ }
[type=“submit”] {
  /* 'attribute' */ }
Attribute selectors
  • Attribute selectors
  • (which include classes and pseudo-classes)
  • Select elements based on any attributes or exposed state
  • This is where we have the most control & flexibility
OddBird.net with red outlines around only the nav links which have a data-nav attribute
Establish reusable patterns
  • Great for defining reusable patterns
  • So they become the backbone of our design systems
css…
#send { /* 'id' */ }
ID selectors
  • Finally ID selectors
  • Valid ID’s are required to be unique on a page
OddBird.net with red outlines around only the logo
One-off overrides
  • Should only ever select one element per page
  • Extremely targeted/specific styles
css…
h1#page-title { /* … */ }
  • We can combine these simple parts
  • Into compound selectors…
Diagram of `p.summary:focus-within` with a solid pink arrow from the entire selector to a solid circle labeled subjects
  • Where every part referring to the same subject
  • Or describe relationships with combinators…
css…
p.summary a:hover { /* descendant */ }
p.summary > a:hover { /* child */ }
p.summary + a:hover { /* next */ }
p.summary ~ a:hover { /* future */ }
Complex Selectors (combined)
  • To form complex selectors
Diagram of `p.summary a:hover` with a dashed blue arrow from `p.summary` to an outer dashed circle labeled context, and a solid pink arrow from `a:hover` to an inner solid circle labeled subjects
  • Where there’s still only one subject (the far right compound)
  • But now different parts refer to different elements,
  • Providing context.
  • This is extremely powerful!
  • We can use selectors…
css…
h2:has(> button[aria-expanded="false"]) + div {
  display: none;
}
Disclosure Widgets by Adrian Roselli
  • to express fairly complex ideas
  • Read from right to left…
  • This selects a div that comes after an h2
  • (which has a button child, that is not expanded)
  • Look at all that information we’re communicating to the browser!

Specificity is a “Heuristic

a practical assumption that approximates the goal
  • But what we’re worried about now
  • Is the specificity of a selector in the cascade
  • And for this step the browser uses a heuristic
  • a practical assumption

More Explicit Selectors
are Likely
More Important

  • That narrowly targeted selectors like IDs
  • are likely more important than broad selectors like element types.
  • That makes some sense, right?
  • One-offs override reusable patterns,
  • which override element defaults,
  • which override global initial values?
css…
/* 1 IDs, 2 attributes, 2 element type */
/* specificity: 1,2,2 */
form#contact button[type='submit']:active {
  border-color: seaGreen;
}
  • To compare, we count up how many terms we used in each selector,
  • And which category those terms belong to
  • Here the border-color declaration has a selector
  • With 1 ID
  • 2 Attributes
  • 2 Element types
  • A specificity of 1, 2, 2
Our old coin sorter, with all slots crossed off except for the largest one with coins in it, and an arrow pointing at those coins that says 'move on'
  • The total isn’t what matters
  • We again compare the columns one at a time

Like Versioning [1,0,2] vs [0,3,2] vs [0,2,3]

  • Like software versions
  • We can look at the first number
  • That might give us a winner already

Move on when tied [0,3,2] vs [0,2,3]

  • We only have to look at the next column (attributes)
  • If there’s a tie in the first

Heuristics Are Assumptions

  • It’s a good heuristic
  • Most of the time
  • But heuristics are assumptions, and…

Assumptions Often Fail

Especially “At Scale

  • Assumptions always fail at some point
  • So the more code we write,
  • (The larger our code base)
  • The more likely we are to find those exceptions

When specificity falls short We Need an Escape Hatch

(still meaningful and expressive)

  • When that happens, when specificity isn’t enough
  • We need an escape hatch
  • that’s still meaningful and expressive
  • That’s why layers were added to the cascade
  • We shouldn’t be afraid of specificity
  • We should have tools to manage it

Some Selectors Help Manage Specificity

  • There are also some new selectors
  • (and old selector tricks)
  • That we can use to manage specificity
  • Selector-by-selector
css…
#example { /* [1,0,0] */ }
[id="example"] { /* [0,1,0] */ }
Reduce ID specificity
  • ID’s are often used in forms, aria-roles, etc
  • But sometimes ID specificity is too much for what we’re trying to do
  • Remember that IDs are attributes!
  • We can use an attribute selector instead,
  • Selecting the same element, with lower specificity
css…
.example { /* [0,1,0] */ }
.example.example.example { /* [0,3,0] */ }
Increase attribute/ID specificity
  • We can also increase the specificity
  • Of a class, attribute, or ID by repeating it
  • In a compound selectors (all strung together)
  • This changes specificity, without changing what we select

:is() and :where() Select The Same Things

  • The :is() and :where() selectors
  • Can also be used to manipulate specificity
  • They always select the same things
css…
/* (a) AND ALSO (nav .active) */
a:where(nav .active) { color: black; }
a:is(nav .active) { color: black; }
  • The overlap between what’s outside
  • (in this case an anchor tag)
  • and what’s inside
  • (in this case the active class inside a nav element)
  • Both parts have to match the same element
css…
/* (h1 a) AND ALSO (main > *) */
h1 a:where(main > *) { color: black; }
h1 a:is(main > *) { color: black; }
Diagram of `footer a:where(nav .active)` with a dashed blue arrow from both `footer` and `nav` to overlapping outer dashed circles labeled context, and solid pink arrows from both `a` and `.active` to overlapping inner solid circles with the overlap labeled subjects
  • Both contexts have to be present
  • Both subjects have to match
Same diagram but with the `:is` pseudo-class replacing the `:where` pseudo-class
  • They select the same elements

:is() and :where() Can Group Selectors

css…
h1 a:hover, h1 a:focus,
h2 a:hover, h2 a:focus,
h3 a:hover, h3 a:focus,
h4 a:hover, h4 a:focus,
h5 a:hover, h5 a:focus {
  text-decoration: underline;
}
css…
:is(h1, h2, h3, h4, h5) a:where(:hover, :focus) {
  text-decoration: underline;
}

:is() and :where() Also Impact Specificity

:where() Removes Specificity

css…
/* nav a.active { 0,1,2 } */
nav a:where(.active) { /* 0,0,2 */ }
a:where(nav .active) { /* 0,0,1 */ }
*:where(nav a.active) { /* 0,0,0 */ }

:is() takes Highest Specificity

css…
:is(a, .b, #c .d) { /* 1,1,0 */ }
a:is(.b, #c .d) { /* 1,1,1 */ }
css…
a:where(#logo, .sponsor .logo) {
  /* specificity: 0,0,1 */
}

a:is(#logo, .sponsor .logo) {
  /* specificity: 1,0,1 */
}
Comparing Specificity

It doesn’t matter Which Selector Matches!

html…
<a class="sponsor logo">Still 1,0,1</a>
css…
a:is(#fakeID, .sponsor .logo) { /* … */ }
One external type, One internal ID

CSS Nesting

Also relies on :is() selector

Support data from CanIUse.com on the css-nesting feature
[Can I Use]
css…
header {
  h1 { /* header h1 */ }
  > .title  { /* header > .title */ }
  + p { /* header + p */ }
}
Familiar Sass Syntax
css…
header {
  h1 { /* header h1 */ }
  > .title  { /* header > .title */ }
  + p { /* header + p */ }
  body & { /* body header */ }
}
Using Relative Selectors
css…
.card {
  @media (width > 20em) {
    display: flex;
  }
}
We can nest at-rules
css…
ol, ul {
  > p { /* :is(ol, ul) > p */ }
  .sidebar & { /* .sidebar :is(ol, ul) */ }
}
Using :is() under the hood
css…
/* :is(button, .btn, #my-btn) */
button, .btn, #my-btn {
  &:focus,
  &:hover,
  &:active { /* 1,1,0 */ }
}
Specificity is different from Sass

While we’re here… :not() and :has()

:is() & :not() & :has() Use Same Specificity

Use :not() For Excluding Elements

(inside matches are removed from outside matches)

css…
/* (p) UNLESS (.warning)  */
p:not(.warning) { /* … */ }
Excluding v Overriding [present, debug]

Use :has() For Selecting Context

(we can move the subject!)

css…
form:has(:focus) { /* form with focus */ }
button:has(svg) { /* button with icon */ }
New :has() Selector
css…
form:focus-within { /* form with focus */ }
Shortcut with better support
css…
.card:has(> figure:first-child) { /* image card */ }
.card:not(:has(img)) { /* card without image */ }
input:has(+ .error) { /* input followed by error */ }
Allows Relative Selectors
Una Kravets [present, debug]
Has light/dark selection [present, debug]
Support data from CanIUse.com on the css-has feature
[Can I Use]
css…
article:has(h1, .title) a {
  color: red;
}

article h1 a {
  color: green;
}
The Cascade (as a funnel) [present, debug]
  • After specificity,
  • Another new feature in the cascade

Scope Is New

Rolling out this year!

Different from the 2014 version
Support data from CanIUse.com on the css-cascade-scope feature
[Can I Use]
A grid of overlapping
'scope' columns (buttons, cards, logins)
and 'layer' rows
(default, theme, component).
Each scope crosses all layers,
and each layer crosses all scopes.

1. Avoid Naming Conflicts

(across large teams & projects)

2. By Expressing Membership

(through lower boundaries & proximity)

Scope Proximity

css…
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
DOM Proximity
Scope proximity demo [present, debug]

@scope (<root>) { … }

Cascade & Inheritance, Level 6
css…
@scope (.light-theme) {
  a { /* similar to simple nesting… */ }
}
@scope (.dark-theme) {
  a { /* but the _closer_ scope root wins… */ }
}
Cascade & Inheritance, Level 6
Scope proximity demo [present, debug]

Scope Boundaries

css…
.title { /* global */ }
.post .title { /* nested */ }

.post__title { /* BEM */ }
Membership is distinct from ancestry
Media component with contents that are out of scope
A “donut scope” with lower boundaries
wireframe of a site, with multiple nested components
A “donut scope” with lower boundaries

Build-tools Provide Scoped Styles

BEM, CSS Modules, Vue, JSX, Stylable, etc

css…
.post__title { /* BEM */ }
.title[data-JKGHJ] { /* Vue */ }
Maintain uniqueness

@scope (<root>) to (<boundary>) {…}

Cascade & Inheritance, Level 6
css…
@scope (.media) to (.content) {
  img { /* only images that are "in scope" */ }
}
Cascade & Inheritance, Level 6
Visualizing scope [present, debug]
Scope boundary demo [present, debug]

Nested <style> Scopes

html…
<article>
  <style scoped>
    p { color: green; } 
  </style>
  <p>This paragraph will be green.</p>
</article>

<p>This paragraph won't!</p>
❌ This won’t work (yet)
html…
<article>
  <style>
    @scope { p { color: green; } }
  </style>
  <p>This paragraph will be green.</p>
</article>

<p>This paragraph won't!</p>
✅ This works

Different from CSS Nesting

Scope has… Defined Relationship

Always child or descendant

Scope… Doesn’t Add Specificity

Scope has… Lower Boundaries

css…
@scope (.block-name) to (.block-content) {
  .title { /* only inside the block! */ }
}
Use scope to narrow context (BEM blocks)
css…
button {
  &:hover { background: hotPink; }
  &[aria-pressed=true] { border: thick solid teal; }
  @media (width > 30em) { padding-inline: 1em; }
  & + & { border-inline-start: 1ch; }
}
Use nesting to provide variants (BEM modifiers) and other relationships

Different from Shadow-DOM Encapsulation

Diagram shows a widget with solid boundaries, which cannot be penetrated in either direction (global styles can't get in, widget styles can't get out)
Encapsulation is designed for isolated DOM widgets
Diagram shows a component with porous boundaries, all styles can penetrate, or establish their own lower boundaries
Scope is designed for a unified system
  1. Scoped styles win
  2. Shadow styles lose
The Cascade (as a funnel) [present, debug]
  • The final step in the cascade

Order of Appearance

  • Order of appearance
  • Guarantees a single answer
css…
button {
  background: red;
  background: oklch(0.5 0.2 0);
}
  • That will come in handy when we talk about resilience
  • Old browsers will discard the new syntax
  • at parse time, part of the filtering step before we cascade
  • That will allow the well-supported value to win
  • But browsers that understand oklch() color syntax
  • Will keep that second value around, and it will override the first
The Cascade (as a funnel) [present, debug]

And that’s the cascade, in 7 steps…

  1. Who requested it, and how much do they care?
  2. Does it come from the shadow DOM?
  3. Is it an inline style?
  4. What layer is it in?
  5. How specific is the selector?
  6. How close is the scope root?
  7. And which style was requested ‘last’?

Cascade output… Single
(possibly empty)
Cascaded Value

for each property of each element

  • The input to the cascade
  • Was a list of declared values
  • (for each property on each element)
  • The output now
  • Is a single (possibly empty) cascaded value

There are Still Missing Values

Inheritance tomorrow!
  • Since some values are potentially empty
  • We need a process to fill them in!
  • We’ll talk about that tomorrow, along with…

And also… Custom Properties

a.k.a CSS Variables
  • Custom Properties (CSS variables)
  • Which can play a unique role in that process
  • That will start to move us out of theoretical physics
  • Into more applied use-cases and demos

The Cascade is Our Most Powerful Feature

  • Hopefully (today) a better understanding of cascade…
  • What it is, how it works
Kylo Ren and Rey
standing in front of an entire star war
with spaceships and lasers
and AT-AT walkers
  • Why it exists (to bring balance)
  • Where we fit into the larger process
Yoda reaching out to control the force, surrounded by jungle swamp
  • And ways we can use it
  • To write more expressive style rules
We're Not Alone, with browser logos, a guy in construction clothes carrying a laptop, and ET
  • As part of our style collaboration
  • With browsers, users,
  • and (hopefully) aliens
  • The cascade is what makes all of this possible.
« resources

Smashing Workshop

Cascading Styles

Bring this workshop to your company.
Slide Controls

View:

Navigate slides using the arrow-keys.