slides.oddbird.net/layers/aea-sf22/

Cascading Layers of !mportance

slides.oddbird.net/layers/aea-sf22/

@ An Event Apart, San Francisco

Web for all. Web on everything.

W3C Mission, Design Principles

It is required that HTML be a common language between all platforms

WWW Project

…This implies no device-specific markup, or anything which requires control over fonts or colors.

WWW Project

The fact we can control a paper page is really a limitation of that medium.

– John Allsopp, 2000

@media prefers-reduced-motion

@supports container-type: inline-size

Animation of twitter-like posts appearing

We’re designing dynamic content with unknown collaborators on an infinite and unknowable canvas, across operating systems, interfaces, writing-modes, & languages

– me

HTML totally eliminates any visual creativity that a document’s designer might have.

– Roy Smith, 1993

The web would have become a giant fax machine where pictures of text would be passed along.

– Håkon Lie

This proposal tries to soften the tension between the author and the reader.

– Håkon Lie

The author often wants to give the documents a distinct look and feel

– Håkon Lie

…the user will set preferences to make all documents appear more similar.

– Håkon Lie

The user/browser specifies initial preferences and hands the remaining influence over to the document [authors].

– Håkon Lie

Provide hints that the browser may or may not use.

– Håkon Lie

<button style='color: blue'></button>
<button style='color: blue'></button>
<button style='color: blue'></button>
<button style='color: blue'></button>
<button style='color: blue'></button>
<button style='color: blue'></button>
<button></button>
<button></button>
<button></button>
button { color: violet; }
<link rel="stylesheet" href="so-many-hints.css">
<style>...</style>

An ordered list (cascade) of style sheets. Referenced from the same document.

– Håkon Lie

💥🙈⁉️💥

<button style='color: blue'></button>
button { color: violet; }

Multiple or Unknown ⁉️

color: teal;
color: violet;
color: oklch(79.269% 0.171 70.67);

On every html element
For every css property
We need exactly one value

Rules for… Cascading filters out
Inheritance fills in

Mechanical coin sorter on a table,
with a ramp at the top
and a small-to-large series of slots

Officially… 🖥 User Agent Styles

body { margin: 8px; }
a:link { color: blue; }
div, section, article, p, ol, ul /* etc */ {
display: block;
}
h1, h2, h3, h4 /* etc */ {
font-weight: bold;
font-size: something big I dunno;
}

👥 User Styles

Establish desired preferences

Cascade 🗺 Origins

  1. 🖥 User Agent Defaults (applied first)
  2. 👥 User Preferences (override defaults)
  3. 🎨 Author Styles (so powerful)

If conflicts arise the user should have the last word

– Håkon Lie

Simplified Influence

h1 { font-size: 24pt !important; } /* 100% */
h2 { font-size: 20pt; } /* 0% */
  1. ❗🎨 Author Important (goes first)
  2. ❗👥 User Important (override authors)
  3. ❗🖥 User Agent Important (most power)
  1. 🖥 User Agent Defaults
  2. 👥 User Preferences
  3. 🎨 Author Styles
  4. ❗🎨 Author Important
  5. ❗👥 User Important
  6. ❗🖥 User Agent Important

Authors 👎🏼 Override Styles

We probably wrote ourselves

Users 👍🏼 Protect Styles

From future origins

Often Isolated

  1. 🎨 Author Styles
  2. ❗🎨 Author Important
  3. ❗…
  4. ❗…

(out of order…) CSS Selectors & Specificity

How Specifically

Is a selector targeting elements

More Specific Styles
Likely
More Important

Heuristic

a practical assumption that approximates the goal

Four layers

  1. Universal * (go first)
  2. Element types
  3. Reusable .classes & [attributes]
  4. Unique #IDs (most power)

Assumptions Often Fail

Especially “At Scale

/* …default table styles… */
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
{
border-color: black;
}

One Flexible Layer

  1. Universal *
  2. Element types
  3. Reusable .classes & [attributes]
  4. Unique #IDs
.block .element.modifier { /* 3 */ }
.block__element--modifier { /* 1 */ }
.🤬-bootstrap {
font-weight: bold !important;
}
@layer settings {}
@layer tools {}
@layer generic {}
@layer elements {}
@layer objects {}
@layer components {}
@layer overrides {}
  1. @layer settings { … }
  2. @layer tools { … }
  3. @layer generic { … }
  4. @layer elements { … }
  5. @layer objects { … }
  6. @layer components { … }
  7. @layer overrides { … }
  1. @layer Utilities { … }
  2. @layer Components { … }
  3. @layer Themes { … }
  4. @layer Frameworks { … }
  5. @layer Resets { … }
@layer reset {
audio[controls] { display: block; }
[hidden] { display: none !important; }
}
@layer {
#menu .dropdown .menu-item {
/* ✅ specificity! */
}

.menu-item { /* … */ }
}
@layer my-defaults {
#menu .dropdown .menu-item { /* … */ }
}

@layer my-overrides {
.menu-item { /* ✅ layer! */ }
}
@layer default { /* least powerful */ }
@layer theme { /* … */ }
@layer components { /* more powerful */ }

/* unlayered styles: most powerful */
@import url(low.css) layer(low); /* new layer! (1) */
@layer medium { /* new layer! (2) */ }
@layer low { /* (1 again) */ }
@layer high { /* new layer! (3) */ }
@layer low { /* (1 again) */ }
@import url(low.css) layer(low); /* (1) */
@layer low { /* (1) */ }
@layer low { /* (1) */ }
@layer medium { /* (2) */ }
@layer high { /* (3) */ }

Explicit Layer Order

Define it once, at the start

/* establish layer order */
@layer low, medium, high;

/* add code to layers as needed */
@import url(medium.css) layer(medium);
@layer high {}
@layer low {}
@layer medium {}
@layer components {
@layer state {}
}

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

@layer system.theme {}
@layer system.components {}
@import url(system.css) layer(system);

@layer system.theme {}
@layer system.components {}

@layer system.override { /* local overrides! */ }
@import url(bootstrap.css) layer(bs.external);

@layer bs.local {
/* anything here will override bootstrap */
}
@layer components {
@layer defaults, themes, state;
}

Language Heuristics Don’t Boss Us Around

Like Origins, ❗️mportant Layers Reverse

  1. Resets
  2. Themes
  3. Components
  4. ❗mportant Components
  5. ❗mportant Themes
  6. ❗mportant Resets

Authors 👎🏼 Override Styles

We probably wrote ourselves

👍🏼 Protect Styles

From future layers

@layer reset {
[hidden] { display: none !important; }
}

Prioritize -> Layers
Protect -> Importance

Getting Started

my recommendations, your milage may vary

/* first & easy to find */
@layer defaults, components, utilities;
<style>/* keep this before linked styles */
@layer defaults, components, utilities;
</style>
<link rel="stylesheet" href="">

Then Layer Dependencies

resets, design systems, frameworks, libraries…

Clearly define… Each Tool’s Priority

Clearly define… Tool-Specific Overrides

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

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

For flexibility Layer Everything

Once we use un-layered styles, that’s as high as we can go

/* Vue example */
<template></template>
<script></script>

<style>
@layer components {
/* all our component styles */
}
</style>

Good Tools Let Us Use CSS Features

(otherwise ‘tools’ become obstacle)

¯\_()_/¯

You do you (no gate-keeping)

Encourages Nuanced & Explicit Priorities

X Overrides Y
Because
‘Components’ Override ‘Defaults’

.contact–submit__focus { /* … */ }
#contact button[type=submit]:focus { /* … */ }
[id="example"] {
/* ID selector, class specificity */
}
:where(#example) {
/* ID selector, zero specificity */
}

👎🏼 Override Styles
In Previous Code

(often in lower layers)

👍🏼 Protect Styles
From Future Code

(often in higher layers)

Use unlayered for Prototyping & DeBugging

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

Broad-Reaching Scopes
&&
Narrow Component Scopes

@scope ([data-scope='media']) to ([data-scope]) {
/* Stop scopes from overlapping? */
}

prototype… Now in Chromium Canary

about://flags - experimental web platform features

Yoda leans on a walking stick among leaves

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

– Yoda (almost)