« resources

Smashing Conference

Cascading Styles

the language is the architecture

workshops.oddbird.net/smashing25/cascade/

Cmd/Ctr-K for Controls

A poll… OOCSS, ITCSS,
SMACSS, BEM, CUBE,
Utility-First, other

What are you trying? Are you happy with it?

  • ✅ Love it
  • ⚠️ Could be improved
  • ❌ Needs to change
  • ‘Branded’ conventions
  • Built up over 20 years of CSS

Branded… Conventions are Useful

(as a point of reference)

  • What I call the ‘branded’ conventions
  • BEM, SMACSS, etc
  • These are systems that someone is marketing to us
  • And they can be useful as a point of reference
  • But it’s also important to remember…

Different Project Needs

  • No single solution will fit every project
  • Or every team
  • We’re all working under different constraints…

Different Team Structures

  • Different team structures
  • Different budgets & priorities
  • And those differences matter…

Just… 🤷🏻‍♀️ Conventions

Your mileage will vary

  • All just conventions
  • A set of guidelines someone found useful
  • In their org, their team, their code
  • Your mileage will vary

Just because InstaSalesFace Did It

Doesn’t mean we all need to

  • Just because instaSalesFace did it
  • Doesn’t mean it will help anyone else
  • In fact, the tools people use at that scale
  • Are often absurdly overblown for the rest of us
  • They’re trying to solve problems most developers don’t have
  • So there’s not a single correct approach
avatar

I think they are all pretty neat and I see the point…

— Chris Coyier

  • I tend to agree with Chris Coyier
  • They’re all pretty neat…
avatar

But I’m a bunch of years into this and still generally… write the selector I need and then style with that.

— Chris Coyier

There’s no Miracle Cure

Conventions won’t save us

  • Important to say up-front, there’s no miracle cure
  • If we refuse to weed the garden over time
  • Then the garden will have weeds
  • That work doesn’t go away because we switch to utility classes,
  • Or move our styles into Javascript

It’s a Process

Conventions can develop over time

  • Maintaining a convention, like weeding, is a process
  • Not a technical problem to ‘solve’ and move on
screenshot
OddBird
  • I’d say we do the same at OddBird
  • But I’m going to flesh that out a bit more today
  • And talk about the decisions we’re making
  • And how we think about ‘the selector we need’

Poetic CSS @ OddBird

  • Start with functional HTML
  • Use the whole language
  • Don’t reinvent browser features
  • Follow the cascade
  • Say what you mean
  • Use minimal constraints
  • Build the essentials, then enhance

HTML has Built-In Functionality

avatar

I decided to stop using the word semantics. Instead I talk about the UX of HTML

— Vasilis van Gemert, The UX of HTML

avatar

Instead of explaining the meaning of a certain element, I show them what it does.

— Vasilis van Gemert, The UX of HTML

HTML also has Built-In Names

Don’t like naming things? Then Stop Naming Everything

  • .btn » button
  • .btn-submit » button[type=submit]
  • .link » a:any-link
  • .hidden » [hidden]
  • .active » [aria-current]
Learn about aria-current

We talk about CSS Cascading & Inheritance

  1. Filtering
  2. Cascading (includes specificity)
  3. Defaulting (includes inheritance)
  4. Resolving
  5. Formatting
  6. Constraining
Value Processing (or ‘value resolution’)
  • Both are steps in a larger process called value resolution
  • Or value processing…

Value resolution is The Foundation of CSS

scss…
this-slide {
  $my-color: red;
  $my-color: yellow; /* winner! */

  background-color: $my-color;

  $my-color: green;
  $my-color: powderBlue;
}
Follow Steps In Code
css…
this-slide {
  --my-color: red;
  --my-color: yellow;

  background-color: var(--my-color);

  --my-color: green;
  --my-color: powderBlue; /* winner! */
}
Follow Value Processing

CSS code… A Series of Commands A Bucket of Properties

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

  • Before a browser can render the page
  • Every CSS property
  • on every HTML element
  • must have a single value
  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)
  • Each step here 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

These are all 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
  • Whenever a property is set more than once
  • We need to 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.

Filtering Finds Relevant Declarations

css…
color: blue;
A CSS Declaration

Could be called Declaration Gathering

Building a list of declarations requires filtering our CSS to only the relevant declarations.

A Relevant Declaration

(determined one element at a time)

  1. In a stylesheet that applies (see media attr)
  2. Not in a false conditional rule (see at-rules)
  3. In a selector that matches the HTML Element
  4. Is syntactically valid
Result is a (maybe empty) list of ‘declared’ values

CSS valid syntax Depends on Type Safety

  • <integer> / <number> / <dimension> etc…
  • <length> / <angle> / <time> etc…
  • <calc-sum>
  • <color> / <hue> / <alpha-value>
  • <image> / <position>
  • <string>
  • <url>
  • <custom-ident> / <dashed-ident>
  • etc.
All CSS Types

The color property Only Accepts <color> Values

css…
html { color: 3em; }
3em is a <length>, not a <color>
css…
html { color: teal; }
teal is a <named-color>, a <color> sub-type
css…
html {
  color: teal;
  color: 3em;
}
css…
html {
  color: teal;
  /* color: 3em; */
}
Invalid declarations can be filtered

only one color We’re Done! 🎉

Value Resolution

  1. Filtering (“parse time validation”)
  2. Cascading
  3. Defaulting
  4. Resolving
css…
html {
  color: var(--my-color);
  --my-color: teal;
  --my-color: 3em;
}
Valid Syntax
css…
.ಠ_ಠ {
  --(╯°□°)╯: ︵┻━┻;
}
credit: Tab Atkins

Value Resolution

  1. Filtering (“parse time validation”)
  2. Cascading
  3. Defaulting
  4. Resolving 👀👀👀👀

Declarations containing The var() function

Are not evaluated at parse time

Value Resolution

  1. Filtering (“parse time”)
  2. Cascading
  3. Defaulting
  4. Resolving (“computed value time”)

Might become… Invalid At Computed Value Time

Sometimes called “IACVT” in Github issues

Declarations containing The var() function
or if() function
or any --custom() function

Are not evaluated at parse time

Invalid At Computed Value Time [present, debug]

Value Resolution

  1. Filtering
  2. Cascading (resolve conflicts)
  3. Defaulting
  4. Resolving
css…
button          { background: gray; }
.action         { background: darkblue; }
[type=“submit”] { background: darkgreen;
                  background: var(--submit); }
#send           { background: maroon; }
Declarations Conflict
The Cascade (as a funnel) [present, debug]
  • The cascade itself has a 7 steps to it
  • 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?
Mechanical coin sorter on a table, with a ramp at the top and a small-to-large series of slots
  • We can think of each layer here as 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 -

Working backwards… Order of Appearance

  • Order of appearance
  • Guarantees a single answer
css…
html {
  background: red;
  background: teal;
}
  • If we set the same property multiple times
  • Only one can apply,
css…
html {
  /* background: red; */
  background: teal;
}
  • And ‘last one wins’ will always give us
  • that single answer
css…
html {
  color: teal;
  color: oklch(55% 0.09 195); /* discarded?? */
}
Useful Progressive Enhancement
css…
html {
  color: teal;
  /* color: oklch(55% 0.09 195); */
}
Some Browsers…
css…
html {
  color: teal;
  color: oklch(55% 0.09 195);
}
Let them both cascade!
css…
html {
  /* color: teal; */
  color: oklch(55% 0.09 195);
}
Let them both cascade!

You can use it and not use it at the same time, because it works and it doesn’t work at the same time. It’s Quantum CSS! It’s Magic!

Order styles… From General to Specific

  • Since later styles override earlier styles
  • We want to start with our broad brush strokes

The Cascade exists for Building Systems

css…
/* broad defaults */
button { background: rebeccapurple; }

/* narrower patterns */
button.danger { background: maroon; }

/* one-off overrides */
button[type=submit] { background: forrestGreen; }
Upside-down headshot of Harry Roberts.

Harry Roberts Inverted Triangle CSS

2014
Inverted triangle with
range across the top,
and specificity/explicitness
down the sides
Harry Roberts, Inverted Triangle CSS

Narrow » Broad Reach How Many Elements?

Low » High Specificity What Selector Weight?

Generic » Explicit What Desired Impact?

From p {} through .text-center {}

Inverted triangle with layers,
wide top to narrow bottom:
settings, tools, generics, elements,
objects, components, overrides
Harry Roberts, Inverted Triangle CSS
A graph
with specificity on the vertical axis,
and code line numbers horizontal,
divided into layers,
and a line showing that
specificity should only increase
throughout the code base
Harry Roberts, Inverted Triangle CSS

Andy Bell CUBE CSS

2020
avatar

Macro Before Micro

— Andy Bell, CUBE CSS

  • Something they all share
  • Working from the macro to the micro
  • Broad strokes, before minute details
avatar

Most of the work is already done for you with global and high-level styles.

— Andy Bell, CUBE CSS

  1. Composition
  2. Utilities
  3. Blocks
  4. Exceptions

Natalie Downe CSS Systems

2008

A CSS System Overall Structure & Individual Components

  • Overall structure & individual components
  • These are distinct, and separate
  1. General Styles (resets, type, colors)
  2. Helper Styles (forms, errors, alerts)
  3. Page Structure
  4. Page Components
  5. Overrides (avoid when possible)
From CSS Systems

Nicole Sullivan OOCSS

2009
  • Nicole Sullivan’s OOCSS
  • (came out around the same time)
  • Highlights similar ideas

Beyond Reusable Components

  • Objects are not just components, because
  • Not just components are reusable…

Components Share Patterns

Avoid even unique component styles

  • But the patterns that we use
  • across components
Selectors (Level 4) [present, debug]
  • Which leads us into selectors and specificity
  • Maybe the most feared and hated part of the cascade…
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 in phrases…
  • 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!
css…
/* expressive browser-ux */
form:has(:invalid) [type="submit"] {}

/* we do all the work??? */
.form__btn--submit--invalid {}
  • We can reference els & attrs in selectors
  • Also have access to pseudo-classes & pseudo-elements
  • Extended with ARIA attributes as needed
  • Selectors help enforce/encourage semantic HTML patterns
  • When we reduce this to a class-name string,
  • We lose all the meaningful built-in associations
css…
a[href*="://"] { /* external links */ }
a[href^="https:"] { /* secure links */ }
a[href$=".pdf"] { /* pdf links */ }

button[aria-pressed=true] { /* pressed buttons */ }
img:not([alt]) { /* images without alt text */ }
  • There’s so much we can do
  • Without ever coming up with our own names
css…
.flow > * + * {
  margin-block-start: var(--flow-space, 1em);
}
Axiomatic CSS and Owl Selectors by Heydon Pickering
  • Selector combinators let us expand on that
  • Defining relationships

Intelligent” Selectors

No “intervention by the author”

Semantic CSS With Intelligent Selectors by Heydon Pickering

But we get lost… In Specificity Battles

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…
* { /* universal */ }
p { /* type */ }
.summary { /* attribute */ }
#call-to-action { /* id */ }
Simple Selectors
  • So simple selectors…
  • (the bits that we build into complex and compound 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…
/* 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

Again… Generic to Explicit

Again… Macro to Micro

Inverted triangle with layers,
wide top to narrow bottom:
settings, tools, generics, elements,
objects, components, overrides
Harry Roberts, Inverted Triangle CSS
A graph
with specificity on the vertical axis,
and code line numbers horizontal,
divided into layers,
and a line showing that
specificity should only increase
throughout the code base
Harry Roberts, Inverted Triangle CSS
  1. Elements grouped by Type
  2. Classes grouped by Effect
  3. IDs grouped by Component
From CSS Systems
  1. General Styles (resets, type, colors)
  2. Helper Styles (forms, errors, alerts)
  3. Page Structure
  4. Page Components
  5. Overrides (avoid when possible)
From CSS Systems
Inverted triangle with layers,
wide to narrow -
the top layers are sass-only, no output,
then global css layers,
and finally scoped components
Harry Roberts, Inverted Triangle CSS

Modern selectors can ‘Hack’ Specificity

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

CSS Nesting

See details on the Web Features explorer
css…
a {
  &:any-link { color: blue; }

  &:focus, &:hover, &:active {
    color: deepPink;
  }
}
Great for variants
css…
.card {
  @media (width > 20em) {
    display: flex;
  }
}
We can nest at-rules
css…
article {
  h2 {}
  .thumbnail {}
}
This would be better with @scope
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

The :is() Selector

See details on the Web Features explorer

The :where() Selector

See details on the Web Features explorer
html…
<nav>
  <a href="#">not selected</a>
  <a href="#" class="active">selected!</a>
</nav>
css…
nav a:is(.active)
nav a:where(.active)
:is() & :where() work the same
  • The :is() and :where() selectors
  • Can also be used to manipulate specificity
  • They always select the same things

: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

🌶️ Hot Take Specificity is Brilliant

(but)

Our teams & projects Grew Faster Than Our Controls

Selectors (Level 4) [present, debug]

@scope

See details on the Web Features explorer
Interop 2025

1. Avoid Naming Conflicts

(across large teams & projects)

2. By Expressing Membership

(through lower boundaries & proximity)

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

.post__title { /* BEM */ }
Membership is distinct from ancestry

@scope (<root>) { … }

Cascade & Inheritance, Level 6

Different from CSS Nesting

Scope has… Defined Relationship

Always child or descendant

Scope… Doesn’t Add Specificity

Scope has… Lower Boundaries

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

In the cascade Scope Proximity

Another heuristic!
css…
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
DOM Proximity
Scope proximity demo [present, debug]
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]
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

See details on the Web Features explorer
There’s also a polyfill
  • Cascade Layers are relatively new, but
  • Well supported across browsers,
  • For almost 3 years now
Inverted triangle with
range across the top,
and specificity/explicitness
down the sides
Harry Roberts, Inverted Triangle CSS
Inverted triangle with layers,
wide top to narrow bottom:
settings, tools, generics, elements,
objects, components, overrides
Harry Roberts, Inverted Triangle CSS
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
css…
/* establish layer order */
@layer one, two, three;

/* add code to layers as needed */
@import url(two.css) layer(two);
@layer three {}
@layer one {}
@layer two {}
  • So define the order up front
  • Generally, imports have to come first
  • Layer lists are allowed before imports
  • Order matters!
css…
@layer components {
  @layer state {}
}

/* access nested layers */
@layer components.state {}
Nesting Layers
css…
/* system.css */
@layer theme {}
@layer components {}
Nesting Layers (in files)
css…
@import url(system.css) layer(system);

@layer system.theme {}
@layer system.components {}
Nesting Layers (from imports)
css…
@import url(system.css) layer(system);

@layer system.theme {}
@layer system.components {}
@layer system.custom {}
Nesting Layers (from imports)
  • Add your own sub-layer to an imported layer!
css…
@import url(bootstrap.css) layer(bootstrap.theirs);

@layer bootstrap.ours {
  /* anything here will override bootstrap */
}
css…
@layer components {
  @layer defaults, themes, state;
}
Order Nested Layers

Not Escalating ❗️importance

Managing Priorities

More Cascade Control
Fewer Specificity Hacks

Getting Started

my recommendations

(your mileage may vary)

Define a A Layer Order

See example layer orders
css…
/* add as the first styles */
@layer reset, framework, components, utilities;
Start simple, and append as needed…
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

If Possible Layer Overall Architecture

html…
<style>
@layer reset, framework, components, utilities;
@import url() layer(reset);
@import url() layer(framework);
</style>
Use (non-nested) imports

Prioritize Dependencies

*Including inter-org dependencies

  • Like resets, design systems, component libraries
  • Anything not project-specific

Clearly define… Each Tool’s Priority

Clearly define… Tool-Specific Overrides

css…
@import url(bootstrap.css) layer(bootstrap.vendor);

@layer bootstrap.overrides {
  /* anything here will override bootstrap */
}

As you go, Update Layer Order

Working from Lowest to Highest

A graph
with specificity on the vertical axis,
and code line numbers horizontal,
divided into layers,
and a line showing that
specificity should only increase
throughout the code base
Harry Roberts, Inverted Triangle CSS
Harry Roberts, Inverted Triangle CSS
Harry Roberts, Inverted Triangle CSS
Harry Roberts, Inverted Triangle CSS
Harry Roberts, Inverted Triangle CSS
html…
<template></template>
<style>
@layer components {
  /* … */
}
</style>
Single File Components

Don’t (Usually) Need Layers Per-Component

As useful, Layer Inside Components

Once We Start Layering

*the styles will be hidden from older browsers

There’s No Harm Layering More

Encourages Nuanced & Explicit Priorities

For flexibility… Layer Everything

Use un-layered for Prototyping & DeBugging

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.
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…
Element-Attached (Inline) Styles [present, debug]
  • Important styles can be used to override inline styles
  • But when both styles are normal, or both important
  • Then inline styles always win
  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

CSS Variables Are Excellent Inline

Dynamic CSS Layouts [present, debug]
The Cascade (as a funnel) [present, debug]
  • With context, we’re mostly talking about shadow DOM
  • And like origins & layers,
  • We’re back to importance reversing things

🌗 (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
  • We’ll see this importance-reversal behavior again
  • in other steps of the cascade
Important Shadow Context [present, debug]

(demo if needed)

The Cascade (as a funnel) [present, debug]
  • So let’s keep going through them
  • We talked a bit about origins
  • But you can see there are a few we left out

For Intermediate Styles

in transitions and animations

  • 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
  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
override ❗Important
override Animation

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>
Technically part of the author origin
  • 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, usually

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

We’ll need inheritance for those…
  • Since some values are potentially empty
  • We need a process to fill them in!
  • That will mean inheritance for some properties
  • And the initial value for others

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 Conference

Cascading Styles

Bring this workshop to your company.
Slide Controls

View:

Navigate slides using the arrow-keys.


Color Settings