slides.oddbird.net/workshops/23-smashing-de/1-cascading-2/

Cascading Styles pt. 2

slides.oddbird.net/workshops/23-smashing-de/1-cascading-2/

@ Smashing Conf Freiburg

Free us to… Use Expressive Selectors

.form-action--submit { /* … */ }
form button[type=submit] { /* … */ }

Some Selectors Help Manage Specificity

[id="example"] {
/* ID selector, class specificity */
}

:is() and :where() Take Entire Selectors

a:is(body.home #logo) { /* … */ }
a:where(body.home #logo) { /* … */ }

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

The union of both inside & outside selectors

/* (a) AND ALSO (nav .active) */
a:where(nav .active) { color: black; }
a:is(nav .active) { color: black; }
/* (h1 a) AND ALSO (main > *) */
h1 a:where(main > *) { color: black; }
h1 a:is(main > *) { color: black; }

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

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

Commas for OR

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

Example From User Agent

table[rules=cols i] > tfoot > tr > td,
table[rules=cols i] > tfoot > tr > th,
table[rules=all i] > tfoot > tr > td,
table[rules=all i] > tfoot > tr > th
{
/* … */
}
table:where(
[rules=cols i], [rules=cols i]
) > tfoot > tr > :is(th, td)
{ /* … */ }

:is() and :where() Have Different Specificity

Use :where() To Remove Specificity

a { color: blue; }
a:hover { color: rebeccapurple; }
nav a.active { color: black; }
/* nav a.active { 0,1,2 } */
nav a:where(.active) { /* 0,0,2 */ }
a:where(nav .active) { /* 0,0,1 */ }

But is() takes specificity From Highest Internal Selector

:is(a, .b, #c .d) { /* 1,1,0 */ }
a:is(.b, #c .d) { /* 1,1,1 */ }

Comparing Specificity

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

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

It doesn’t matter Which Item Matches!

  a:is(#logo, .sponsor .logo) { /* … */ }
  <a class="sponsor logo">Still 1,0,1</a>

Use :not() For Excluding Elements

(inside matches are removed from outside matches)

/* (p) UNLESS (.warning)  */
p:not(.warning) { /* … */ }

New :has() Selector

form:has(:focus) { /* form:focus-within */ }
button:has(svg) { /* icon button */ }
.card:has(> figure:first-child) { /* image card */ }
.card:not(:has(img)) { /* card without image */ }
input:has(+ .error) { /* input followed by error */ }

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

Which one wins?

article:has(h1, .title) a {
color: red;
}

article h1 a {
color: green;
}

CSS Nesting

Also relies on :is() selector

button, .btn {
background: rebeccapurple;
color: white;
}
button, .btn {
background: rebeccapurple;
color: white;

&:focus,
&:hover,
&:active
{
background: teal;
}
}
button, .btn { /* … */ }

:is(button, .btn):focus,
:is(button, .btn):hover,
:is(button, .btn):active
{ /* … */ }
ol, ul {
> p { /* :is(ol, ul) > p */ }
.sidebar & { /* .sidebar :is(ol, ul) */ }
}
.card {
@layer defaults {
display: flex;
}

@layer variations {
@media (min-width: 30em) {
display: grid;
}
}
}

For now… Nested Selectors Must
Start With a Symbol

Not Valid (Yet)

ol, ul {
li { /* … */ }
nav & { /* … */ }
}

Solutions (For Now)

ol, ul {
& li { /* … */ }
:is(nav) & { /* … */ }
}

Different from… Sass Behavior & Specificity

1. Avoid Naming Conflicts

(across large teams & projects)

2. By Expressing Membership

(through lower boundaries & proximity)

.light-theme a { color: purple; }
.dark-theme a { color: plum; }
@scope (.light-theme) {
a { /* similar to simple nesting… */ }
}
@scope (.dark-theme) {
a { /* but the _closer_ scope root wins… */ }
}
.title { /* global */ }
.post .title { /* nested */ }

.post__title { /* BEM */ }
Media component with contents that are out of scope
wireframe of a site, with multiple nested components

Build-tools Provide Scoped Styles

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

.post__title { /* BEM */ }
.title[data-JKGHJ] { /* Vue */ }
@scope (.media) to (.content) {
img { /* only images that are "in scope" */ }
}
<article>
<style scoped>
p { color: green; }
</style>
<p>This paragraph will be green.</p>
</article>

<p>This paragraph won't!</p>
<article>
<style>
@scope {
p { color: green; }
}
</style>
<p>This paragraph will be green.</p>
</article>

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

Different from CSS (Descendant) Nesting

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)
Diagram shows a component with porous boundaries,
all styles can penetrate, or establish their own lower boundaries

By default…

  1. Scoped styles win
  2. Shadow styles lose

prototype… Now in Chromium Canary

about://flags - experimental web platform features

Finally… Order of Appearance

(last style wins)

.resilient {
color: green;
color: color(display-p3 0 1 0);
}

The Cascade is Our Most Powerful Feature

The cascade is Getting Major Upgrades

The cascade Facilitates Collaboration

Yoda leans on a walking stick among leaves

A Jedi uses the [Cascade] for knowledge and defense, never for attack.

– Yoda (almost)