slides.oddbird.net/csswg/f2f2102/

CSSWG Proposals

@ Virtual Face-to-Face
  1. Revisiting @container

  2. Revisiting @scope
@media (width > 45em) -
diagram of two different card layouts
on either side of a media-query breakpoint
(viewport width > 45em) -
cards both using large layout,
but one is in a small sidebar container
(container width > 45em) -
cards each using the appropriate layout
for the container it is in

2020 Proposals

  • David Baron: @container
    limited by containment
  • Brian Kardell: switch()
    limited to paint
Nested grid-item containers
inside the main container,
also use small card layout
We need to define the containers
for our cards to query

Establishing Containers

.sidebar, main, .grid-item {
contain: size layout;
}

Size containment Is Too Restrictive

1D Size Containment

(proposed by David Baron)

  • inline-size (most useful & most possible)
  • block-size?
  • width?
  • height?

DBaron’s @container Rule

@container <selector> (<container-media-query>) {
/* ... rules ... */
}
@container .sidebar, main, .grid-item (width > 40em) {
.card { /* ... card layout ... */ }
h2 { /* ... responsive type? ... */ }
}
/* query *nearest contained ancestor* */
@container (width > 40em) {
.card { /* ... */ }
h2 { /* ... */ }
}
DOM-tree diagram of all the cards,
and their respective containers.
Each card resolves against
the most immediate contained ancestor.
.card {
display: grid;
grid-gap: 0.5em;
}

@container (min-width: 40ch) {
.card {
grid-template: "img text" auto / minmax(10em, 30%) 1fr;
grid-gap: 1em;
}
}
h2 {
font-size: 1.2em;
line-height: 1.4;
}

@container (min-width: 40ch) {
h2 {
font-size: 1.6em;
line-height: 1.2;
}
}
More complex DOM tree,
where the main element has containment,
and h2's query their container.
Any h2 that is inside the main container,
without any intermediate container in the tree
will respond to the main container.

Rules in @container apply when…

  • There’s an ancestor with appropriate containment
  • The laid out size of the most immediate container
    matches the query condition

Query against computed values

  • em queries can resolve based on computed font size?
  • Can we query more than just dimensions?

Potential issues

  • Difficult to query grid-track
  • Container is always external

Alternative selector syntax

@container (inline-size < 40em) {
.card { /* ... */ }
}

.card:container(inline-size < 40em) {
/* ... */
}

With nesting syntax

.card:container(inline-size < 40em) {
& h2 { /* ... */ }
}

Other Questions

CQ Proposal Resources

  1. Revisiting @container
  2. Revisiting @scope

The Scope Goal

  1. Avoid Naming Conflicts

    (across large teams & projects)
  2. By Expressing Membership

    (through lower boundaries & proximity)
wireframe of a site, with multiple nested components
.title { /* global */ }
.post .title { /* nested */ }

.post__title { /* BEM */ }
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
<div class="dark-theme">
<a href="#">plum</a>
<div class="light-theme">
<a href="#">also plum???</a>
</div>
</div>
CSS Wishlist:
Proximity should impact the cascade

Why not 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 High-Impact & DOM-Centric

(non-goals for this proposal)

  • 1:1 relationship widget/scope
  • Boundaries defined in the DOM
  • Styles are isolated by default
  • Proximity overrides specificity

Conventions and build-tools Provide Scoped Styles

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

Diagram shows a component with porous boundaries,
all styles can penetrate, or establish their own lower boundaries

Scope is Low-Impact & Style-Centric

(goals for this proposal)

  • Scopes are defined in CSS
    • …can be re-used across components
    • …can overlap & cascade together
  • Priority is mostly handled by ownership
  • Specificity overrides proximity

Expressing Membership

.post__title { /* BEM */ }
.title[data-scope=post] { /* … */ }
.x1234 { /* … */ }

Proposal… @scope & :scope

The @scope rule

/* @scope (<root>#) [to (<boundary>#)]? { … } */
@scope (.tabs) to (.panel) { /* … */ }

The :scope selector

@scope (.tabs) to (.panel) {
:scope { /* targeting the scope root */ }
.light-theme :scope .tab { /* contextual styles */ }
}

Implied :scope ancestor

@scope (.tabs) {
.tab { /* :scope .tabs */ }
}

For boundary-matching

@scope (.tabs) to (.panel) {
.panel { /* lower boundary is in-scope */ }
}

For boundary-matching

@scope (.tabs) to (.tabs) {
/* to (:scope .tabs) */
}

@scope (.tabs) to (:scope) {
/* single-element scope (needs use-cases?) */
}

For selector-matching

@scope (.tabs) to (.panel, .tabs) {
.tab { /*
:scope .tab:not(:scope :is(.panel, .tabs) .tab)
*/
}
.tabs { /*
:scope .tabs:not(:scope :is(.panel, .tabs) .tabs)
*/
}
}

For specificity

@scope (.tabs, #options) {
.tab { /*
specificity: [1, 1, 0]
as-though: :is(.tabs, #options) .tab
*/
}
}

For proximity

@scope (.light-theme) { a { color: purple; } }
@scope (.dark-theme) { a { color: plum; } }
<div class="dark-theme">
<a href="#">plum</a>
<div class="light-theme">
<a href="#">purple</a>
</div>
</div>

For proximity

@scope (.light-theme) { a { color: purple; } }
@scope (.dark-theme) { a { color: plum; } }
<div class="dark-theme light-theme">
<a href="#">plum (source order)</a>
</div>

Specificity takes precedence

@scope (.hero) { #call-to-action { text-decoration: none; } }
@scope (.typeset) { a { text-decoration: undeline; } }
<header class="hero">
<div class="typeset">
<a id="call-to-action">purple (specificity)</a>
</div>
</header>

Potential “Donut” Selector?

concept suggested by Lea Verou in TAG review

@scope (.tabset) to (.panel) { .tab { /* ... */ }  }
.tab:in(.tabset / .panel) { /* ... */ }