OddBird CSS Notebook
Notes towards the future of CSS, from Miriam Suzanne & OddBird
https://css.oddbird.net/
2024-02-11T20:20:37Z
© 2024 OddBird
https://css.oddbird.net/favicon.svg
Eleventy
OddBird
birds@oddbird.net
2024-02-11T20:20:37Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Changes to variable scope and function result<span class="widont"> </span>syntax</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Changes to variable scope and function result<span class="widont"> </span>syntax</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><h2 id="author">Author <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#author">¶</a></h2>
<p>Miriam Suzanne</p>
<p>(Based heavily on a custom-function proposal by Tab Atkins)</p>
<h2 id="intro">Intro <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#intro">¶</a></h2>
<p>In order to reduce code repetition,
ensure consistency across a project,
and encourage best practice,
authors have often turned to third-party
CSS pre-processors
(Sass, Less, PostCSS, Stylus, etc)
to define custom reusable ‘macros’.
These generally fall into two categories:</p>
<ul>
<li><strong>Functions</strong> return CSS <em>values</em> –
like a string, color, or length.
They can only be used <em>inside</em>
the value space of a CSS property.</li>
<li><strong>Mixins</strong> return CSS <em>declarations</em>
or entire <em>rule blocks</em>.
They can only be used <em>outside</em>
the value space of a CSS property.</li>
</ul>
<p>CSS already provides a wide range
of built-in functions,
such as <code>calc()</code>, <code>minmax()</code>, and many more.
Ideally, custom functions would work in a similar way,
but prefixed with a dashed-ident
to avoid future compatibility issues.
For a simple example:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> --negative <span class="token punctuation">(</span>--value<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>-1 * <span class="token function">var</span><span class="token punctuation">(</span>--value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">--negative</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--gap<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<p>CSS does not yet have built-in mixins,
though several have been proposed
in discussions of this feature.
A simple mixin might look something like this:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@mixin</span> --button <span class="token punctuation">(</span>--face<span class="token punctuation">,</span> --text<span class="token punctuation">,</span> --radius<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--face<span class="token punctuation">,</span> teal<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--color</span><span class="token punctuation">:</span> <span class="token function">color-mix</span><span class="token punctuation">(</span>in lch<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--text<span class="token punctuation">,</span> white<span class="token punctuation">)</span> 85%<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--background<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--border-color</span><span class="token punctuation">:</span> <span class="token function">color-mix</span><span class="token punctuation">(</span>in lch<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--text<span class="token punctuation">,</span> white<span class="token punctuation">)</span> 80%<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--background<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@result</span></span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--background<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> medium double <span class="token function">var</span><span class="token punctuation">(</span>--border-color<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--radius<span class="token punctuation">,</span> 3px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25lh 2ch<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">button[type='submit']</span> <span class="token punctuation">{</span> <span class="token atrule"><span class="token rule">@apply</span> <span class="token function">--button</span><span class="token punctuation">(</span>rebeccaPurple<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="token punctuation">}</span><br /><span class="token selector">button.danger</span> <span class="token punctuation">{</span> <span class="token atrule"><span class="token rule">@apply</span> <span class="token function">--button</span><span class="token punctuation">(</span>maroon<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="token punctuation">}</span></code></pre>
<h2 id="discussion">Discussion <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#discussion">¶</a></h2>
<ul>
<li>Discussion on CSSWG Drafts
<a href="https://github.com/w3c/csswg-drafts/issues/9350">Proposal: Custom CSS Functions & Mixins (#9350)</a></li>
<li>Existing proposal for
<a href="https://github.com/w3c/csswg-drafts/issues/7490">Declarative custom functions (#7490)</a></li>
<li>Issue tracker
for this explainer:
<a href="https://github.com/oddbird/css-sandbox/issues">OddBird CSS Sandbox Issues</a></li>
</ul>
<p>There are several other relevant discussions
in the CSS Working Group,
that predate this proposal:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7879">[css-variables-2] Custom shorthands with @property #7879</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7490">Declarative custom functions #7490</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5624">[css-variables?] Higher level custom properties that control multiple declarations #5624</a></li>
</ul>
<p>(If there are more I haven’t found,
please <a href="https://github.com/oddbird/css-sandbox/issues">let me know</a>.)</p>
<!--
..######..##.....##.##.....##.##.....##....###....########..##....##
.##....##.##.....##.###...###.###...###...##.##...##.....##..##..##.
.##.......##.....##.####.####.####.####..##...##..##.....##...####..
..######..##.....##.##.###.##.##.###.##.##.....##.########.....##...
.......##.##.....##.##.....##.##.....##.#########.##...##......##...
.##....##.##.....##.##.....##.##.....##.##.....##.##....##.....##...
..######...#######..##.....##.##.....##.##.....##.##.....##....##...
-->
<h2 id="summary--goals">Summary & Goals <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#summary--goals">¶</a></h2>
<p>Features often change
as they move from (generally imperative)
pre-processors into CSS –
taking on different affordances and constraints
appropriate for a declarative,
client-side language:</p>
<ul>
<li>How would CSS-native mixins and functions
differ from pre-processors?</li>
<li>What extra functionality or limitations
come from providing these features
in the browser?</li>
</ul>
<p>From a language/implementation perspective
mixins and functions
are entirely distinct features –
they live at different levels of the syntax,
and come with different complications.
If we pursue both,
we likely want to define them at
different levels of a specification,
or even in different specifications.</p>
<p>Removing the reliance on pre-processors
would further simplify maintenance for CSS authors,
while providing new client-side functionality:</p>
<ul>
<li>Passing cascaded custom-properties as arguments.</li>
<li>Adding media/support and other client-side conditions.</li>
</ul>
<p>My goal here is to explore
what would be possible with each feature,
where we could re-use syntax between them,
and how we might move forward
with implementing them.</p>
<p>I am not expecting this to be the final shape
for either feature,
but I want to capture the state of the conversation,
and help move it forward.
If these features are officially
adopted by the working group,
further development can be broken
into individual specs and issues.</p>
<!--
.####.##....##.########.########.########..########..######..########
..##..###...##....##....##.......##.....##.##.......##....##....##...
..##..####..##....##....##.......##.....##.##.......##..........##...
..##..##.##.##....##....######...########..######....######.....##...
..##..##..####....##....##.......##...##...##.............##....##...
..##..##...###....##....##.......##....##..##.......##....##....##...
.####.##....##....##....########.##.....##.########..######.....##...
-->
<h2 id="author-interest">Author Interest <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#author-interest">¶</a></h2>
<p>There is some (incomplete) data
from the HTTP Archive project
that can help us understand
how authors are using Sass currently:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5798">Stats on Scss usage of control flow, conditional logic, nesting, custom functions #5798</a></li>
</ul>
<p>I also ran a small <a href="https://front-end.social/@mia/110833306689188274">survey on Mastodon</a>:</p>
<blockquote>
<p>“What are the most common custom functions or mixins
that you define/use in a css pre-processor?”</p>
</blockquote>
<p>The answers included:</p>
<ul>
<li>(Functions) Conversion from pixel to <code>rem</code> units</li>
<li>(Functions) random number generators</li>
<li>(Functions) Color contrast</li>
<li>(Mixins) Named shorthands for common media queries</li>
<li>(Mixins) Generating output from object for-each loops (like Sass Maps)</li>
<li>(Mixins) Reusable component styles</li>
<li>(Mixins) Complex solutions, like scroll-shadows or gradient text</li>
<li>(Both) Fluid typography settings</li>
<li>(Both) Complex <code>calc()</code> shorthands for various situations</li>
</ul>
<p>Some of these would be possible to achieve
in CSS with a declarative syntax,
without additional new functionality.
Others (like loops) would require imperative control structures.</p>
<p>While some of these (like <code>random()</code>)
are already being discussed for built-in functions,
others (like <code>color-contrast()</code>)
may be simpler to solve in user-space.
It has been very difficult for the CSSWG
to settle on a long-term solution
for the entire platform,
while an individual team would be
more able to change their approach gradually over time.
By capturing that logic in a single place
(like a custom function),
many changes could be made without
any invasive re-write of the code base.</p>
<p>The ability to declare this logic in CSS
rather than a pre-processor
would provide several benefits:</p>
<ul>
<li><em>Reduce the external dependencies</em> and build steps
required in order to generate the code</li>
<li><em>Reduce the file size delivered</em> from the server
(though this may be negligible after compression &
increased client-side processing)</li>
<li><em>use custom properties as arguments</em>
so that the mixins or functions
could respond to changes in the cascade</li>
<li><em>use media/container/support conditions</em>
as part of the internal logic</li>
</ul>
<!--
.########.....###....########.....###....##.....##..######.
.##.....##...##.##...##.....##...##.##...###...###.##....##
.##.....##..##...##..##.....##..##...##..####.####.##......
.########..##.....##.########..##.....##.##.###.##..######.
.##........#########.##...##...#########.##.....##.......##
.##........##.....##.##....##..##.....##.##.....##.##....##
.##........##.....##.##.....##.##.....##.##.....##..######.
-->
<h2 id="defining-parameters">Defining parameters <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#defining-parameters">¶</a></h2>
<p>Both functions and mixins
rely on a <code><parameter-list></code> syntax.
Each <code><parameter></code>
in the <code><parameter-list></code>
consists of three parts:</p>
<ul>
<li><code><name></code> (required)
which is a <code>dashed-ident</code></li>
<li><code><syntax></code> (default: <code>*</code>)
similar to the <code>syntax</code> descriptor in <code>@property</code></li>
<li><code><default-value></code> (default: <code>guaranteed invalid</code>)
which is any value that matches the syntax</li>
</ul>
<p>Defining all three aspects in the function prelude
(name, type, and default)
can make the syntax over-complicated.
My initial proposal
included special <code>@property</code>-like
descriptor blocks to make that possible.</p>
<p>Since then,
the discussion has moved towards
a more concise approach
using a comma-separated list.</p>
<p>Authors can provide names only:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--my-function</span><span class="token punctuation">(</span>--param-a<span class="token punctuation">,</span> --another-param<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span></code></pre>
<p>Optionally,
they can also provide a default value:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--my-function</span><span class="token punctuation">(</span><br /> <span class="token property">--param-a</span><span class="token punctuation">:</span> 1em<span class="token punctuation">,</span><br /> <span class="token property">--another-param</span><span class="token punctuation">:</span> <span class="token string">'this is a string'</span><br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span></code></pre>
<details data-alert="note" open="">
<summary>Note:</summary>
<div><p>Since the list is comma-separated,
this would require
<a href="https://github.com/w3c/csswg-drafts/issues/9539">better handling of arguments with commas</a>
in<span class="widont"> </span><span class="caps">CSS</span>.</p>
</div>
</details>
<p>Finally,
authors could define
a syntax for any parameter,
using the <code>type()</code> function
along side the name.
This would work with or without default values:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--my-function</span><span class="token punctuation">(</span><br /> --param-a <span class="token function">type</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --another-param <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">:</span> 1em<br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span></code></pre>
<!--
.########.##.....##.##....##..######..########.####..#######..##....##..######.
.##.......##.....##.###...##.##....##....##.....##..##.....##.###...##.##....##
.##.......##.....##.####..##.##..........##.....##..##.....##.####..##.##......
.######...##.....##.##.##.##.##..........##.....##..##.....##.##.##.##..######.
.##.......##.....##.##..####.##..........##.....##..##.....##.##..####.......##
.##.......##.....##.##...###.##....##....##.....##..##.....##.##...###.##....##
.##........#######..##....##..######.....##....####..#######..##....##..######.
-->
<h2 id="defining-a-function-the-function-rule">Defining a function: the <code>@function</code> rule <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#defining-a-function-the-function-rule">¶</a></h2>
<p>In order to define a custom function,
we need several bits of information:</p>
<ul>
<li><code>function-name</code> (required)</li>
<li><code>parameter-list</code> (optional - see above)</li>
<li>Some amount of internal logic using <code>function-rules</code></li>
<li>A returned <code>result</code> value</li>
</ul>
<p>The proposed syntax
(with a few adjustments)
could look something like:</p>
<pre><code>@function <function-name> [( <parameter-list> )]? {
<function-rules>
result: <result>;
}
</code></pre>
<p>The <code>function-name</code> is a dashed-ident.
If multiple functions have the same name,
then functions in a higher cascade layer take priority,
and functions defined later have priority
within a given cascade layer.
This matches the behavior of other name-defining at-rules.</p>
<p>It may also be useful to define an intended ‘return type’
(e.g. <code>color</code> or <code>length</code>) for the function,
so that it can be validated at parse time.
Like custom properties,
there is still a chance that a function’s output
will be <em>invalid at computed value time</em>,
but we can at least ensure that
the function is intended to return an appropriate syntax
for the context where it is being called.</p>
<p>Extending the above syntax,
I would imagine re-using the <code>type()</code> function
in the prelude:</p>
<pre><code>@function <function-name> [( <parameter-list> )]? [returns type(<syntax>)]? {
<function-rules>
result: <result>;
}
</code></pre>
<p>I would expect <code><syntax></code> to allows the same
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax#values">subset of CSS Types</a>
provided by the <code>syntax</code> descriptor of the <code>@property</code> rule.
Maybe it would be possible to remove
the requirement for quotes around a syntax in this context?</p>
<h3 id="returning-values">Returning values <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#returning-values">¶</a></h3>
<p>There have been several syntax options discusses
for returning a <code><result></code> value,
but it seems to me like the simplest
and most familiar would be a descriptor
called something like <code>result</code> or <code>output</code>.
This would help re-enforce
the declarative nature of functions,
since it can be treated similar to other declarations:
the last <code>result</code> is used if multiple are present.</p>
<p>Like custom properties:</p>
<ul>
<li>The <code><result></code> can contain any valid CSS value-space syntax</li>
<li>This value has <code>invalid at computed value time</code> behavior</li>
</ul>
<p>Since functions exist in the value space,
<code><function-rules></code> will not contain any other
(non-custom) CSS properties,
so the single <code>result</code> descriptor should stand out.
If multiple results are encountered,
the last result takes precedence
(consistent with other descriptors and properties).
This is discussed in more detail below.</p>
<details data-alert="note" open="">
<summary>Note:</summary>
<div><p>Tab covers
<a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1930463996">declarative execution in the <span class="caps">CSSWG</span> issue</a>
with a bit more<span class="widont"> </span>detail.</p>
</div>
</details>
<h3 id="function-rules">Function rules <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#function-rules">¶</a></h3>
<p>The <code><function-rules></code> can include custom property declarations
(which are scoped to the function),
as well as conditional at-rules
(which may contain further nested
custom properties and <code>result</code>s).
Element-specific conditions (such as container queries)
would be resolved for each element that calls the function.</p>
<p>My assumption
would be that custom properties
defined inside the function
are not available
on elements where the function is called.
However, it’s clear that authors will expect
to reference external custom properties
from inside functions –
using some variation of dynamic scope,
and ‘shadowing’ behavior.</p>
<p>As far as I can tell,
only custom properties, args/variables,
and conditional rules
are useful inside a function definition.
Functions have no output
besides their returned value,
so nested selectors, built-in properties,
and name-defining rules
are not necessary or meaningful.
I don’t think there’s any need for these things
to invalidate the entire function,
but they should be ignored and discarded.</p>
<p>An example function
using conditional rules
to return one of multiple values:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--sizes</span><span class="token punctuation">(</span><br /> --s <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --m <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --l <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /><span class="token punctuation">)</span> returns <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--min</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>inline-size < 20em<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--min<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--s<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>20em < inline-size < 50em<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--min<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--m<span class="token punctuation">,</span> 1em + 0.5vw<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>50em < inline-size<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--min<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--l<span class="token punctuation">,</span> 1.2em + 1vw<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Some functions will also want access to
contextual variables
on the calling elements.
To avoid fully dynamic scoping
of custom properties,
Tab has proposed a second list
of properties that should be available
in the function:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> --my-function <span class="token punctuation">(</span>--arg1<span class="token punctuation">,</span> --arg2<span class="token punctuation">)</span> using <span class="token punctuation">(</span>--var1<span class="token punctuation">,</span> --var2<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* --arg1 and --arg2 can be provided as arguments */</span><br /> <span class="token comment">/* --var1 and --var2 will shadow identically-named variables<br /> in the calling context */</span><br /><span class="token punctuation">}</span></code></pre>
<details data-alert="note" open="">
<summary>Note:</summary>
<div><p>Tab covers
<a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1930463996">variable scoping in the <span class="caps">CSSWG</span> issue</a>
with a bit more<span class="widont"> </span>detail.</p>
</div>
</details>
<h3 id="calling-functions">Calling functions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#calling-functions">¶</a></h3>
<p>Custom functions can be called
from the value space of any property,
with the name of the functions,
followed by parenthesis and
a comma-separated list of arguments:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">--contrast</span><span class="token punctuation">(</span>pink<span class="token punctuation">,</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If we do (eventually) want to support named arguments,
it would ideally use a familiar
declaration syntax:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">--contrast</span><span class="token punctuation">(</span><span class="token property">--color</span><span class="token punctuation">:</span> pink<span class="token punctuation">;</span> <span class="token property">--ratio</span><span class="token punctuation">:</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If positional and named arguments
are allowed in the same function call,
the common convention is to require
all positional values come before any named values
to avoid confusion:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">--contrast</span><span class="token punctuation">(</span>pink<span class="token punctuation">;</span> <span class="token property">--ratio</span><span class="token punctuation">:</span> 0.7<span class="token punctuation">;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We need to allow a broad
syntax for argument values –
including values that contain commas.
There’s an active discussion
about the best way to handle this
more generally in
<a href="https://github.com/w3c/csswg-drafts/issues/9539">issue #9539</a>.
Custom functions should use whatever solution
is agreed on there.</p>
<h3 id="putting-it-all-together">Putting it all together <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#putting-it-all-together">¶</a></h3>
<p>Adapting the fluid ratio function above
to the proposed syntax:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--fluid-ratio</span><span class="token punctuation">(</span><br /> --min-width <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --max-width <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /><span class="token punctuation">)</span> returns <span class="token function">type</span><span class="token punctuation">(</span>percentage<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--min-width<span class="token punctuation">,</span> 300px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--max</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--max-width<span class="token punctuation">,</span> 2000px<span class="token punctuation">)</span>l<br /> <span class="token property">--scale</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--max<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--position</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vw - <span class="token function">var</span><span class="token punctuation">(</span>--min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--fraction</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--position<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--scale<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@return</span> <span class="token function">clamp</span><span class="token punctuation">(</span><br /> 0%<span class="token punctuation">,</span><br /> 100% * <span class="token function">var</span><span class="token punctuation">(</span>--fraction<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> 100%<br /> <span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc-mix</span><span class="token punctuation">(</span><span class="token function">--fluid-ratio</span><span class="token punctuation">(</span>375px<span class="token punctuation">;</span> 1920px<span class="token punctuation">)</span><span class="token punctuation">,</span> 1rem<span class="token punctuation">,</span> 1.25rem<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">calc-mix</span><span class="token punctuation">(</span><span class="token function">--fluid-ratio</span><span class="token punctuation">(</span>375px<span class="token punctuation">;</span> 700px<span class="token punctuation">)</span><span class="token punctuation">,</span> 1rem<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We could also consider moving the <code>mix()</code> logic
into the function:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--fluid-mix</span><span class="token punctuation">(</span><br /> --min-value <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --max-value <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --from-width <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --to-width <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><br /><span class="token punctuation">)</span> returns <span class="token function">type</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--from</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--from-width<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--fluid-min<span class="token punctuation">,</span> 375px<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--to</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--to-width<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--fluid-max<span class="token punctuation">,</span> 1920px<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--scale</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--to<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--from<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--position</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vw - <span class="token function">var</span><span class="token punctuation">(</span>--from<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--fraction</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--position<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--scale<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--progress</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>0%<span class="token punctuation">,</span> 100% * <span class="token function">var</span><span class="token punctuation">(</span>--fraction<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@return</span> <span class="token function">calc-mix</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--progress<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--min-value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--max-value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">--fluid-mix</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 1.25rem<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">--fluid-mix</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 2rem<span class="token punctuation">,</span> 375px<span class="token punctuation">,</span> 700px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<!--
.##.....##.####.##.....##.####.##....##..######.
.###...###..##...##...##...##..###...##.##....##
.####.####..##....##.##....##..####..##.##......
.##.###.##..##.....###.....##..##.##.##..######.
.##.....##..##....##.##....##..##..####.......##
.##.....##..##...##...##...##..##...###.##....##
.##.....##.####.##.....##.####.##....##..######.
-->
<h2 id="defining-a-mixin-the-mixin-rule">Defining a mixin: the <code>@mixin</code> rule <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#defining-a-mixin-the-mixin-rule">¶</a></h2>
<p>Rather than returning a single value,
mixins return entire declarations
and potentially entire nested rule blocks.
While much of the function syntax
could be re-purposed,
we would need an additional way
to manage property scoping –
clearly marking what rule blocks are internal,
and which should be part of the output.</p>
<pre><code>@mixin <mixin-name> [( <parameter-list> )]? {
<mixin-rules>
}
</code></pre>
<p>Again, when there are multiple mixins
that use the same name,
the last mixin with that name
takes precedence.</p>
<h3 id="mixin-rules-and-output">Mixin rules and output <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#mixin-rules-and-output">¶</a></h3>
<p>The simplest approach
to nested rules and output
would be to treat the inside of a mixin definition
the same as any rule-block nested context.
Anything we can put inside a rule block
can be put inside a mixin,
and will be output where the mixin is called
(with any parameters being replaced first).
This will work for many simpler cases:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@mixin</span> --center-content</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span><br /> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.page</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@apply</span> --center-content<span class="token punctuation">;</span></span><br /> <span class="token comment">/*<br /> display: grid;<br /> place-content: center;<br /> */</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@mixin</span> <span class="token selector">--clearfix </span><span class="token punctuation">{</span><br /> <span class="token selector"><span class="token parent important">&</span>::after </span><span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span><br /> <span class="token property">clear</span><span class="token punctuation">:</span> both<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flow-root<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> flow-root<span class="token punctuation">;</span><br /><br /> <span class="token selector"><span class="token parent important">&</span>::after </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.float-container </span><span class="token punctuation">{</span><br /> @apply --clearfix<span class="token punctuation">;</span><br /> <span class="token comment">/*<br /> &::after {<br /> display: block;<br /> content: "";<br /> clear: both;<br /> }<br /><br /> @supports (display: flow-root) {<br /> display: flow-root;<br /><br /> &::after { display: none; }<br /> }<br /> */</span><br /><span class="token punctuation">}</span></code></pre>
<p>This approach doesn’t allow
the mixin to contain any internal logic
scoped to the mixin itself.
Mixins should be able to
use internally scoped custom-properties,
and also optionally <em>output</em> custom properties
as part of the returned rule block.
As things stand,
this doesn’t seem relevant
to anything other than custom properties.
Built-in properties, selectors, and at-rules
are only useful for their output.</p>
<p>Given that this issue is specific to custom properties,
we could consider a flag such as <code>!private</code>.
That flag could be interesting
for custom properties in other contexts,
but I won’t follow that path unless there’s interest.
Alternatively,
we could explicitly mark
blocks of content
with either <code>@output</code> or <code>@private</code> at-rules.</p>
<h3 id="applying-mixins-the-new-apply-rule">Applying mixins: the (new) <code>@apply</code> rule <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#applying-mixins-the-new-apply-rule">¶</a></h3>
<p>In order to apply a mixin,
we use an <code>@apply</code> rule:</p>
<pre class="language-css"><code class="language-css">@apply <mixin-name> [<span class="token punctuation">(</span><argument-list><span class="token punctuation">)</span>]?</code></pre>
<p>The <code><argument-list></code> syntax
should ideally match the function argument notation.</p>
<p>When the mixin is resolved,
the output of the mixin
is inserted where the apply rule was called:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* declaration */</span><br /><span class="token selector">.float-container</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@apply</span> --clearfix<span class="token punctuation">;</span></span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* result */</span><br /><span class="token selector">.float-container</span> <span class="token punctuation">{</span><br /> <span class="token selector">&::after</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span><br /> <span class="token property">clear</span><span class="token punctuation">:</span> both<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flow-root<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> flow-root<span class="token punctuation">;</span><br /><br /> <span class="token selector">&::after</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>There is an additional question
about how to handle mixin output
at the top level of the document
(not nested inside a selector):</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@apply</span> --center-content<span class="token punctuation">;</span></span></code></pre>
<p>As long as there is a selector wrapping the output,
this should not be an issue.
Even if that selector is simply
the parent reference <code>&</code>,
that has a well-defined behavior
at the top level of documents –
referring to the current <code>:scope</code>.
However, if the result is bare declarations
without any selector,
they should be discarded and ignored.</p>
<p>Another example,
from a Sass mixin I’ve used on occasion:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@mixin</span> <span class="token function">--gradient-text</span><span class="token punctuation">(</span><br /> --from-color <span class="token function">type</span><span class="token punctuation">(</span>color<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --to-color <span class="token function">type</span><span class="token punctuation">(</span>color<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> --at-angle <span class="token function">type</span><span class="token punctuation">(</span>angle<span class="token punctuation">)</span><span class="token punctuation">,</span><br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--to</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--to-color<span class="token punctuation">,</span> teal<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--from</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--from-color<span class="token punctuation">,</span> mediumvioletred<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--angle</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--at-angle<span class="token punctuation">,</span> to bottom right<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--from<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--to<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token punctuation">(</span><span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">--gradient</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--angle<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--from<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--to<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gradient<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--from<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span><br /> <span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">h1</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@apply</span> <span class="token function">--gradient-text</span><span class="token punctuation">(</span>pink<span class="token punctuation">,</span> powderblue<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="token punctuation">}</span></code></pre>
<!--
.##..........###....##....##.########.########...######.
.##.........##.##....##..##..##.......##.....##.##....##
.##........##...##....####...##.......##.....##.##......
.##.......##.....##....##....######...########...######.
.##.......#########....##....##.......##...##.........##
.##.......##.....##....##....##.......##....##..##....##
.########.##.....##....##....########.##.....##..######.
-->
<h2 id="layers-of-complexity">Layers of complexity <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#layers-of-complexity">¶</a></h2>
<p>The popular Sass functions and mixins
demonstrate a range of different input needs,
from relatively static shorthands,
to fully imperative control structures.</p>
<h3 id="simple-shorthands">Simple shorthands <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#simple-shorthands">¶</a></h3>
<p>A <code>clearfix</code> mixin
often has no exposed ‘parameters’,
and no internal logic.
When the mixin is invoked,
it will output
the same code every time.
This is useful for maintaining
DRY code (Don’t Repeat Yourself),</p>
<p>Static mixins like this
end up very similar to
‘utility classes’ such as <code>.clearfix</code>.
However, mixins still have the advantage
that they can be applied in CSS,
rather than HTML.
The need for CSS control
comes into focus when combined
with <code>@media</code>/<code>@container</code> and other conditional logic.
There is currently no way in CSS
to write this code without
defining all the custom properties twice:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.dark-mode</span> <span class="token punctuation">{</span><br /> <span class="token property">--background</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><br /> <span class="token property">--text</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token comment">/* more custom props as needed… */</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">html:not(.light-mode)</span> <span class="token punctuation">{</span><br /> <span class="token property">--background</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><br /> <span class="token property">--text</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token comment">/* more custom props as needed… */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Most of the existing proposals around this use-case
would combine conditional logic
with selector logic,
so that both can be defined at once.
In Sass, we might fix this instead
by providing a <code>dark-mode</code> mixin
that can be used multiple times
to output the same declarations
with only minimal repetition:</p>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@mixin</span> <span class="token selector">dark-mode </span><span class="token punctuation">{</span><br /> <span class="token property">--background</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><br /> <span class="token property">--text</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token comment">/* more custom props as needed… */</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.dark-mode </span><span class="token punctuation">{</span><br /> <span class="token keyword">@include</span> dark-mode<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">html</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>.light-mode<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">@include</span> dark-mode<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Using Container Style Queries
might also be an option here.
They can be somewhat <em>mixin-like</em>,
but come with all the limitations
of container queries.
If we set a custom property <code>--mode</code>
on the root <code>html</code> element,
we have to assign properties on a different element
than we query:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.dark-mode</span> <span class="token punctuation">{</span><br /> <span class="token property">--mode</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">html:not(.light-mode)</span> <span class="token punctuation">{</span><br /> <span class="token property">--mode</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--mode</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* The html element cannot query itself */</span><br /> <span class="token selector">body</span> <span class="token punctuation">{</span><br /> <span class="token property">--background</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><br /> <span class="token property">--text</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token comment">/* more custom props as needed… */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>That can cause several problems:</p>
<ul>
<li>There are optimizations and features specific to the root,
that can’t be replicated on other elements.</li>
<li>In other component contexts,
it’s likely to require extra markup.</li>
</ul>
<p>While no-parameter mixins like these
are somewhat common,
it’s much less common to have a
function without parameters,
since a simple value
can be captured in a variable
or custom property instead.</p>
<h3 id="built-in-conditions">Built-in conditions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#built-in-conditions">¶</a></h3>
<p>It can also be useful to provide mixins
that have no author-facing parameters,
but still contain internal logic and conditional statements –
using <code>@supports</code>, <code>@media</code>, or <code>@container</code>:</p>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@mixin</span> <span class="token selector">gradient-text </span><span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> teal<span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">)</span> <span class="token operator">or</span> <span class="token punctuation">(</span><span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to bottom right<span class="token punctuation">,</span> teal<span class="token punctuation">,</span> mediumvioletred<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">-webkit-text-fill-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span><br /> <span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>A mixin like this might even
reference external values
by relying on custom properties
without accepting explicit override parameters:</p>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@mixin</span> <span class="token selector">gradient-text </span><span class="token punctuation">{</span><br /> <span class="token property">--gradient-text-start</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary<span class="token punctuation">,</span> teal<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--gradient-text-end</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-complement<span class="token punctuation">,</span> mediumvioletred<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gradient-text-start<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">)</span> <span class="token operator">or</span> <span class="token punctuation">(</span><span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><br /> to bottom right<span class="token punctuation">,</span><br /> <span class="token function">var</span><span class="token punctuation">(</span>--gradient-text-start<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">var</span><span class="token punctuation">(</span>--gradient-text-end<span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">-webkit-text-fill-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span><br /> <span class="token property">-webkit-background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="accepting-parameters">Accepting parameters <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#accepting-parameters">¶</a></h3>
<p>The most common reason
to use a function or mixin
is the ability to define parameters
that alter the output
based on different input.
For example, a
<code>darken()</code> function
would accept two parameters:
a color,
and an amount to darken that color.</p>
<p>In many cases (like <code>darken()</code>)
the internal function logic
can be represented by an inline calculation
using existing CSS features.
In those situations,
a custom function could still provide
more concise and easy-to-use shorthand
around a more complex <code>calc()</code>
or relative color adjustment.</p>
<h3 id="parameter-conditions">Parameter conditions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#parameter-conditions">¶</a></h3>
<p>Once we allow both parameters
and conditional logic,
the next step would be to allow
parameters to be used in the conditions themselves.
For example:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@mixin</span> <span class="token function">button</span><span class="token punctuation">(</span><span class="token property">--style</span><span class="token punctuation">:</span> outline<span class="token punctuation">,</span> <span class="token property">--shape</span><span class="token punctuation">:</span> pill<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@when</span> <span class="token function">arg</span><span class="token punctuation">(</span><span class="token property">--style</span><span class="token punctuation">:</span> outline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> medium solid<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> teal<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@else</span></span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> teal<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@when</span> <span class="token function">arg</span><span class="token punctuation">(</span><span class="token property">--shape</span><span class="token punctuation">:</span> pill<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="imperative-control-flow">Imperative control flow <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#imperative-control-flow">¶</a></h3>
<p>Some use-cases require
more complex ‘flow control’
such as loops.
For example,
a combination of mixins might generate
a full color-palette
based on a single origin color.
In Sass,
it might looks something like this:</p>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@use</span> <span class="token string">'sass:color'</span><span class="token punctuation">;</span><br /><span class="token keyword">@use</span> <span class="token string">'sass:math'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">@mixin</span> <span class="token function">tint-shade</span><span class="token punctuation">(</span><span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token variable">$name</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$steps</span></span><span class="token punctuation">:</span> 2<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token property">--<span class="token variable">#{$name}</span></span><span class="token punctuation">:</span> <span class="token variable">#{$color}</span><span class="token punctuation">;</span><br /><br /> <span class="token property"><span class="token variable">$step</span></span><span class="token punctuation">:</span> math.<span class="token function">div</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token variable">$steps</span> <span class="token operator">+</span> 1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">@for</span> <span class="token variable">$i</span> <span class="token keyword">from</span> 1 <span class="token keyword">through</span> <span class="token selector"><span class="token variable">$steps</span> </span><span class="token punctuation">{</span><br /> <span class="token property"><span class="token variable">$amount</span></span><span class="token punctuation">:</span> <span class="token variable">$step</span> <span class="token operator">*</span> <span class="token variable">$i</span><span class="token punctuation">;</span><br /> <span class="token property">--<span class="token variable">#{$name}</span>-t<span class="token variable">#{$i}</span></span><span class="token punctuation">:</span> #<span class="token punctuation">{</span>color.<span class="token function">mix</span><span class="token punctuation">(</span>white<span class="token punctuation">,</span> <span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token variable">$amount</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token property">--<span class="token variable">#{$name}</span>-s<span class="token variable">#{$i}</span></span><span class="token punctuation">:</span> #<span class="token punctuation">{</span>color.<span class="token function">mix</span><span class="token punctuation">(</span>black<span class="token punctuation">,</span> <span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token variable">$amount</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">@mixin</span> <span class="token function">theme</span><span class="token punctuation">(</span><span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$type</span></span><span class="token punctuation">:</span> <span class="token string">'complement'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* generate tints and shades for the main color */</span><br /> <span class="token keyword">@include</span> <span class="token function">tint-shade</span><span class="token punctuation">(</span><span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token string">'primary'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">@if</span> <span class="token selector"><span class="token variable">$type</span> == 'complement' </span><span class="token punctuation">{</span><br /> <span class="token property"><span class="token variable">$complement</span></span><span class="token punctuation">:</span> color.<span class="token function">adjust</span><span class="token punctuation">(</span><span class="token variable">$color</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$hue</span></span><span class="token punctuation">:</span> 180deg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">@include</span> <span class="token function">tint-shade</span><span class="token punctuation">(</span><span class="token variable">$complement</span><span class="token punctuation">,</span> <span class="token string">'complement'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">@else if</span> <span class="token selector"><span class="token variable">$type</span> == 'triad' </span><span class="token punctuation">{</span><br /> <span class="token comment">/* logic for triad themes… */</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">/* etc… */</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">html </span><span class="token punctuation">{</span><br /> <span class="token keyword">@include</span> <span class="token function">theme</span><span class="token punctuation">(</span>blue<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The resulting output CSS would be:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* generate tints and shades for the main color */</span><br /> <span class="token property">--primary</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span><br /> <span class="token property">--primary-t1</span><span class="token punctuation">:</span> #5555ff<span class="token punctuation">;</span><br /> <span class="token property">--primary-s1</span><span class="token punctuation">:</span> #0000aa<span class="token punctuation">;</span><br /> <span class="token property">--primary-t2</span><span class="token punctuation">:</span> #aaaaff<span class="token punctuation">;</span><br /> <span class="token property">--primary-s2</span><span class="token punctuation">:</span> #000055<span class="token punctuation">;</span><br /> <span class="token property">--complement</span><span class="token punctuation">:</span> yellow<span class="token punctuation">;</span><br /> <span class="token property">--complement-t1</span><span class="token punctuation">:</span> #ffff55<span class="token punctuation">;</span><br /> <span class="token property">--complement-s1</span><span class="token punctuation">:</span> #aaaa00<span class="token punctuation">;</span><br /> <span class="token property">--complement-t2</span><span class="token punctuation">:</span> #ffffaa<span class="token punctuation">;</span><br /> <span class="token property">--complement-s2</span><span class="token punctuation">:</span> #555500<span class="token punctuation">;</span><br /> <span class="token comment">/* etc… */</span><br /><span class="token punctuation">}</span></code></pre>
<p>I think it would be reasonable
to draw a boundary here,
since CSS is a declarative language.
Adding imperative flows
would likely cause confusion around the execution model.</p>
<!--
.########..########.########....###....####.##........######.
.##.....##.##..........##......##.##....##..##.......##....##
.##.....##.##..........##.....##...##...##..##.......##......
.##.....##.######......##....##.....##..##..##........######.
.##.....##.##..........##....#########..##..##.............##
.##.....##.##..........##....##.....##..##..##.......##....##
.########..########....##....##.....##.####.########..######.
-->
<h2 id="detailed-discussion-and-open-questions">Detailed discussion and open questions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#detailed-discussion-and-open-questions">¶</a></h2>
<h3 id="other-result-syntaxes-for-functions">Other result syntaxes for functions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#other-result-syntaxes-for-functions">¶</a></h3>
<p>Both Lea and I have noted that
it would be useful
if authors could rely on cascade
‘order of appearance’
to provide ‘fallback’ return values.
Sadly, however,
that sort of parse-time fallback
is not possible with dynamic
computed-value-time features
like custom properties or functions.</p>
<p>I initially proposed an at-rule syntax (<code>@return</code>),
arguing that:</p>
<ul>
<li>It helps distinguish
the final returned value from any internal logic
like custom properties and nested rules</li>
<li>Result is not a property,
but looks a lot like one</li>
</ul>
<p>However, <code>result</code>
does act like a property in many ways,
and would help to re-enforce
our familiarity with declarative execution.
While many imperative languages
allow an ‘eager’ <em>first-takes-precedence</em> function return,
CSS and other declarative languages
generally uses a <em>last-takes-precedence</em> approach.
For the same reason,
we should avoid active words like <code>return</code>
that suggest the evaluation is linear
and can be cut short.</p>
<p>François Remy
has proposed setting a custom property
with the same name as the function,
and that property is treated as the resulting value.
Lea Verou suggested making the property name
customizable in the prelude.</p>
<p>I prefer a syntax that is more consistent and reliable.
I don’t see any utility that comes from
allowing this functionality to be renamed in each function,
or requiring that name to be determined by authors,
or putting it in the author’s custom-ident name space.
Those all seem to me like ways of inviting typos and confusion,
without any clear gain.</p>
<p>Matching the function name
seems to me extra fragile –
as you could never rename one
without also updating the other.
Still,
either approach could work,
and provide the same basic behavior.
We can continue to bike-shed the details.</p>
<h3 id="passing-nested-content-to-mixins">Passing nested content to mixins <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#passing-nested-content-to-mixins">¶</a></h3>
<p>Another common feature of Sass mixins
is the ability to pass nested content blocks
into a mixin,
and have the mixin place that content
in a specific context.
This seems like a feature
that could be supported in CSS as well,
but would require another mixin-specific at-rule
(or similar placeholder).
I’ll call it <code>@nested</code> for now:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@mixin</span> --media-medium</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token function">env</span><span class="token punctuation">(</span>--small<span class="token punctuation">)</span> < inline-size < <span class="token function">env</span><span class="token punctuation">(</span>--large<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@nested</span><span class="token punctuation">;</span></span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.grid</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@apply</span> --media-medium</span> <span class="token punctuation">{</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--padding-medium<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>The expected behavior would be
the same as writing:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token function">env</span><span class="token punctuation">(</span>--small<span class="token punctuation">)</span> < inline-size < <span class="token function">env</span><span class="token punctuation">(</span>--large<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--padding-medium<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This seems like something that could be added later,
if necessary.</p>
<h3 id="invalid-function-fallbacks">Invalid function fallbacks <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#invalid-function-fallbacks">¶</a></h3>
<p>Sadly,
last-takes-precedence <code>@return</code> behavior
doesn’t provide the same benefit here
that it has in the cascade –
where invalid declarations
can be discarded at parse time,
falling back on previously declared values.
In order to achieve that,
we would need to limit functions
so that they are the only value in a property.
I don’t think that tradeoff makes sense
for the use-cases I’ve seen.</p>
<p>I’m also not sure it makes sense
to provide function-defined fallback values
to return when arguments provided have invalid syntax.
Ideally, function fallbacks
would be modeled after variable fallbacks –
established where the function is called,
rather than where it is defined.
It’s hard to see where this would fit
in the proposed syntax.</p>
<p>One option would be a <code>var()</code>-like
wrapper function:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">fallback</span><span class="token punctuation">(</span><span class="token function">--contrast</span><span class="token punctuation">(</span>pink<span class="token punctuation">;</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">,</span> black<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We could even use the existing <code>var()</code>,
but that would result in functions and custom properties
sharing a single namespace,
which might not be ideal.
Maybe the proposed function for
<code>first-supported()</code> would also be an option
that has broader use?
This likely needs more bike-shedding.</p>
<h3 id="using-parameters-in-conditional-rules">Using parameters in conditional rules <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#using-parameters-in-conditional-rules">¶</a></h3>
<p>Above,
I used an example with conditional output
using media queries inside the function.
Authors may reasonably wish to take this farther
and use parameters to define the media queries themselves:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--media</span><span class="token punctuation">(</span><br /> <span class="token property">--breakpoint</span><span class="token punctuation">:</span> length<span class="token punctuation">,</span><br /> <span class="token property">--below</span><span class="token punctuation">:</span> length<span class="token punctuation">,</span><br /> <span class="token property">--above</span><span class="token punctuation">:</span> length<br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span>width < <span class="token function">var</span><span class="token punctuation">(</span>--breakpoint<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--below<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span>width >= <span class="token function">var</span><span class="token punctuation">(</span>--breakpoint<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--above<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is a very common use of pre-processor mixins,
and a common use-case for the proposed inline <code>if()</code>
and <code>media()</code> functions as well.</p>
<p>As I understand it,
that will not be possible as written above,
for the same reasons <code>var()</code> is not currently allowed
in media-query conditions.
However,
the issues are specific to cascaded values
that need to be resolved at computed value time.
Passing static arguments from a parameter
should not pose the same problem.</p>
<p>If we had a new way of accessing
values passed in –
I’ll use <code>arg()</code> for the sake of argument –
simple value substitution should be possible:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--media</span><span class="token punctuation">(</span><br /> <span class="token property">--breakpoint</span><span class="token punctuation">:</span> length<span class="token punctuation">,</span><br /> <span class="token property">--below</span><span class="token punctuation">:</span> length<span class="token punctuation">,</span><br /> <span class="token property">--above</span><span class="token punctuation">:</span> length<br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span>width < <span class="token function">arg</span><span class="token punctuation">(</span>--breakpoint<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--below<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span>width >= <span class="token function">arg</span><span class="token punctuation">(</span>--breakpoint<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--above<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* this works fine, since the argument is accessed with `var()` */</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">--media</span><span class="token punctuation">(</span>40em<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--padding<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* this errors, since the argument is accessed with `arg()` */</span><br /> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token function">--media</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--break<span class="token punctuation">,</span> 40em<span class="token punctuation">)</span><span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In the above example,
the <code>padding</code> declaration
would be valid
since a static value
can be passed along to the media query <code>arg()</code> –
but the <code>margin</code> declaration would fail
since it supplies a custom property
to a media query condition.</p>
<p>It’s not clear to me
if parameters used this way
would need to be explicitly marked in advance
for any reason?
As proposed here,
it would be up to function authors
to document and communicate
which parameters can accept cascading variables,
and which can not.</p>
<h3 id="argument-conditions-and-loops">Argument conditions and loops <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#argument-conditions-and-loops">¶</a></h3>
<p>With both mixins and functions
it can be useful to have conditions
based on the arguments passed in.
For example, we might want to pass in
one of several established keywords,
and return a different value
depending which keyword is used:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--link</span><span class="token punctuation">(</span><br /> <span class="token property">--theme</span><span class="token punctuation">:</span> *<span class="token punctuation">;</span></span><br /><span class="token selector">)</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@when</span> <span class="token punctuation">(</span><span class="token function">arg</span><span class="token punctuation">(</span>--theme<span class="token punctuation">)</span><span class="token punctuation">:</span> light<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">env</span><span class="token punctuation">(</span>--link-light<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@else</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">env</span><span class="token punctuation">(</span>--link-dark<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>It’s not clear to me
if the proposed <code>@when</code>/<code>@else</code> features
can be adapted to this use-case,
or if it would need to be
a distinct set of similar flow controls.</p>
<p>Similarly,
as we saw in the tint-shade example earlier,
it can be useful to loop over
a set number of repetitions (for loop)
or a set list of items (each loop).</p>
<p>While these would be helpful features for authors,
they are not required for
(or dependent on)
an initial implementation of mixins or functions.
They feel like distinct features
that would go well together.</p>
<h3 id="can-we-allow-the-calc-sum-syntax">Can we allow the <code><calc-sum></code> syntax? <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#can-we-allow-the-calc-sum-syntax">¶</a></h3>
<p>This question was raised
by <a href="https://github.com/w3c/csswg-drafts/issues/7490#issuecomment-1256880496">Brandon McConnell</a>
in the ‘Declarative Custom Functions’ issue
(see point 5, even though it’s not specific to recursion).
The goal is to provide custom functions
that take raw calc expressions,
without being explicitly wrapped in a nested
<code>calc()</code> function,
similar to the way other math functions work:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.item</span> <span class="token punctuation">{</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>100% - 1em<span class="token punctuation">,</span> 30em<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>On the one hand,
custom property substitution
makes it trivial to capture expressions,
and later call them inside a <code>calc()</code> function.
This already works:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">--l</span><span class="token punctuation">:</span> 100% - 50%<span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>0deg 100% <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--l<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>To take it farther,
we would need to expose the <code><calc-sum></code>
grammar as a valid syntax
for authors to use.</p>
<p>It might also be worth considering
what other syntax/types would be useful to expose –
either for parameters specifically,
or for property registration more generally.
It seems ideal to me
if those lists can be kept in alignment.</p>
<h3 id="what-about-extend">What about <code>@extend</code>? <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#what-about-extend">¶</a></h3>
<p>In Sass,
mixins without parameters also
overlap with the <code>@extend</code> feature,
which is used to combine related classes –
one as an ‘extension’ of the other.
In most cases,
that has the same intended result
as a no-parameter-mixin:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* extends */</span><br /><span class="token selector">.error</span> <span class="token punctuation">{</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> thin solid maroon<span class="token punctuation">;</span><br /><br /> <span class="token selector">&:hover</span> <span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> #fee<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.error--serious</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@extend</span> .error<span class="token punctuation">;</span></span><br /> <span class="token property">border-width</span><span class="token punctuation">:</span> thick<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* mixin */</span><br /><span class="token atrule"><span class="token rule">@mixin</span> error</span> <span class="token punctuation">{</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> thin solid maroon<span class="token punctuation">;</span><br /><br /> <span class="token selector">&:hover</span> <span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> #fee<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.error--serious</span> <span class="token punctuation">{</span><br /> <span class="token atrule"><span class="token rule">@include</span> error<span class="token punctuation">;</span></span><br /> <span class="token property">border-width</span><span class="token punctuation">:</span> thick<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The difference is that a class definition
can be compiled from multiple rule blocks
in different style sheets,
while a mixin generally has one centralized definition.
This is part of the reason
extensions have become less common in Sass –
it can be difficult to reason about their impact.
For now,
I think mixins would provide the similar functionality
without the same complexity.</p>
<p>If we are interested in exploring <code>@extend</code> at some point,
Tab has already written an
<a href="http://tabatkins.github.io/specs/css-extend-rule/">unofficial draft specification</a>
that we can build from.</p>
<h3 id="can-functions-be-chained-or-call-themselves">Can functions be chained, or call themselves? <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#can-functions-be-chained-or-call-themselves">¶</a></h3>
<p>I would expect that it should be possible
to chain function/mixin calls together.
A theme-generating mixin
should be able to reference
a single-color generating mixin or function internally.</p>
<p>It’s less clear to me if recursive function calls
are possible or necessary.
There are likely use-cases for recursion
as a form of looping,
but I’m not sure how central they are.
This doesn’t seem like a feature requirement in level 1.</p>
<h3 id="keyframe-based-mixins-for-interpolated-values">Keyframe-based mixins for interpolated values? <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#keyframe-based-mixins-for-interpolated-values">¶</a></h3>
<p>There has been a lot of recent discussion around
<a href="https://github.com/w3c/csswg-drafts/issues/6245#issuecomment-1715416464">interpolating values between breakpoints</a>
for e.g. responsive typography.
Conceptually, animation keyframes work well
for defining the steps involved –
but in this case the result is not technically animated,
and interpolated values
should ideally not be removed
to the animation origin.</p>
<p>To get around that,
the most recent proposals
involves a new property
(tentatively <code>interpolate</code>)
that would accept a keyframes name
and timeline,
then ‘expand in place’
to represent the declarations
in the referenced <code>@keyframes</code> rule.</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> typography</span> <span class="token punctuation">{</span><br /> <span class="token selector">from</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.2em<span class="token punctuation">;</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.4<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token selector">to</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">h2</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* declaration, this is all pseudo-code */</span><br /> <span class="token property">interpolate</span><span class="token punctuation">:</span> typography --container-size ease-in<span class="token punctuation">;</span><br /><br /> <span class="token comment">/* result, with interpolated values */</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Alan Stearns has pointed out
in conversations
that this is a very mixin-like behavior,
and suggested treating keyframes
as an existing form of mixin,
rather than a new property.
Given the same keyframes above,
we could consider a syntax like:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* mixin, this is all pseudo-code */</span><br /> <span class="token atrule"><span class="token rule">@apply</span> <span class="token function">typography</span><span class="token punctuation">(</span>--container-size<span class="token punctuation">;</span></span> ease-in<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* result, with interpolated values */</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If that clutters the mixin namespace,
another approach might be
requiring dashed-ident mixin names,
and providing some built-in mixins such as:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* declaration, this is all pseudo-code */</span><br /> <span class="token atrule"><span class="token rule">@apply</span> <span class="token function">keyframes</span><span class="token punctuation">(</span>typography<span class="token punctuation">;</span></span> --container-size<span class="token punctuation">;</span> ease-in<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* result, with interpolated values */</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token comment">/* interpolated… */</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<!--
.########..########..####..#######..########........###....########..########
.##.....##.##.....##..##..##.....##.##.....##......##.##...##.....##....##...
.##.....##.##.....##..##..##.....##.##.....##.....##...##..##.....##....##...
.########..########...##..##.....##.########.....##.....##.########.....##...
.##........##...##....##..##.....##.##...##......#########.##...##......##...
.##........##....##...##..##.....##.##....##.....##.....##.##....##.....##...
.##........##.....##.####..#######..##.....##....##.....##.##.....##....##...
-->
<h2 id="prior-art">Prior art <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#prior-art">¶</a></h2>
<h3 id="the-apply-rule-abandoned">The <code>@apply</code> Rule (abandoned) <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#the-apply-rule-abandoned">¶</a></h3>
<details data-alert="note" open="">
<summary>Links:</summary>
<div><p><a href="https://www.xanthir.com/b4o00">Why I abandoned <code>@apply</code></a>
by <strong>Tab<span class="widont"> </span>Atkins-Bittner</strong></p>
</div>
</details>
<p>At one point,
there was a plan
for custom properties to act as a form of mixin,
using the <code>@apply</code> rule.
That proposal was abandoned
as the wrong approach
for several related reasons:</p>
<ul>
<li>Custom properties are value-level syntax,
while mixins are declaration-level</li>
<li>It doesn’t make sense for mixin definitions
to be passed around in the cascade</li>
</ul>
<p>These are not difficult issues to avoid.
I’m working from the premise that:</p>
<ul>
<li>Both function and mixins
should be <em>defined</em> globally,
and not rely on any element-aware
aspects of the cascade.</li>
<li>Similar to e.g. <code>@keyframes</code>,
function and mixin definitions
would still resolve name conflicts
using global cascade features
like <em>layers</em> and <em>order of appearance</em>.</li>
<li>Functions are applied in the <em>value</em> space,
while mixins are applied in the <em>declaration</em> space.</li>
</ul>
<h3 id="container-style-queries-partially-implemented">Container Style Queries (partially implemented) <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#container-style-queries-partially-implemented">¶</a></h3>
<details data-alert="note" open="">
<summary>Links:</summary>
<div><p><a href="https://www.w3.org/TR/css-contain-3/#style-container"><span class="caps">CSS</span> Containment Module Level<span class="widont"> </span>3</a></p>
</div>
</details>
<p>The <code>style()</code> feature of <code>@container</code>
can sometimes be used to approximate mixin behavior.
There are several recent
<a href="https://front-end.social/@chriscoyier/110821892737745155">posts</a>
and <a href="https://chriskirknielsen.com/blog/future-themes-with-container-style-queries/">articles</a>
written about that approach.
However, style queries
share the limitation of other container queries:
<em>we can’t style the container being queried</em>.</p>
<p>Container queries are designed
as a <em>conditional selector</em> mechanism,
for responding to changes in context.
The ancestor/descendant limitation
is required for browsers to separate
selector-matching from value-resolution
on a given element.</p>
<p>However, <em>mixins do not alter selection</em>,
they only ‘bundle’ existing CSS rules and declarations for re-use.
Ideally, these two features should work well together,
so that contextual conditions
can change the arguments passed to a given mixin.</p>
<h3 id="custom-properties-implemented">Custom Properties (implemented) <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#custom-properties-implemented">¶</a></h3>
<details data-alert="note" open="">
<summary>Links:</summary>
<div><p><a href="https://www.smashingmagazine.com/2019/07/css-custom-properties-cascade/"><span class="caps">CSS</span> Custom Properties In The Cascade</a>
by <strong>Miriam<span class="widont"> </span>Suzanne</strong></p>
</div>
</details>
<p>We can also use custom properties to
approximate some basic mixins and functions.
While these tricks can be useful,
they involve significant complexity,
caveats, and limitations:</p>
<ul>
<li>Each ‘function/mixin’ and ‘argument’ is a custom property,
which can only have a single resolved value per element</li>
<li>Arguments are substituted in the function/mixin
<em>before the computed value inherits</em>,
so the logic has to be defined
on every element that should re-calculate a result</li>
</ul>
<h3 id="mixins-and-functions-in-pre-processors">Mixins and functions in pre-processors <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#mixins-and-functions-in-pre-processors">¶</a></h3>
<details data-alert="note" open="">
<summary>Links:</summary>
<div><ul>
<li><a href="https://sass-lang.com/documentation/at-rules/function/">Sass <code>@function</code> documentation</a></li>
<li><a href="https://sass-lang.com/documentation/at-rules/mixin/">Sass <code>@mixin</code>/<code>@include</code> documentation</a></li>
</ul>
</div>
</details>
<p>In addition to parameters,
Sass mixins can accept <em>content blocks</em>.
An example <a href="https://sass-lang.com/documentation/at-rules/mixin/#content-blocks">from the documentation</a>:</p>
<pre class="language-scss"><code class="language-scss"><span class="token keyword">@mixin</span> <span class="token selector">hover </span><span class="token punctuation">{</span><br /> &<span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[disabled]<span class="token punctuation">)</span><span class="token selector">:hover </span><span class="token punctuation">{</span><br /> <span class="token keyword">@content</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.button </span><span class="token punctuation">{</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid black<span class="token punctuation">;</span><br /> <span class="token keyword">@include</span> <span class="token selector">hover </span><span class="token punctuation">{</span><br /> <span class="token property">border-width</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>That might be a useful feature
for CSS mixins as well.
It would be required for the use-case
of creating named conditions.
That use-case may also be solved by the proposed
<code>@when</code> rule and ‘custom media queries’ feature.</p>
<p>Sass provides some built-in core functions,
but (so far) does not provide core mixins.
Likely for that reason,
the HTTP Archive report lists
several commonly-used built-in functions
(<code>if()</code>, and <code>darken()</code>),
but only the most commonly used
custom mixin name (<code>clearfix</code>).</p>
<h3 id="existing-proposal-for-custom-functions">Existing Proposal for Custom Functions <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#existing-proposal-for-custom-functions">¶</a></h3>
<p>In July of 2022,
Johannes Odland proposed
‘<a href="https://github.com/w3c/csswg-drafts/issues/7490">Declarative custom functions</a>’
in the CSS Working Group issue tracker.
Since then,
the proposal has gone through
several revisions and updates.</p>
<p>The current (2023-08-08)
proposal in that thread
suggests that:</p>
<ul>
<li>Functions would be resolved
at the same time as variable substitution</li>
<li>Function parameters defined with a CSSOM ‘syntax’
can be validated at parse time
(like <code>@property</code>-registered variables)</li>
<li>This would be a declarative version
of the more full-featured Houdini API feature</li>
</ul>
<p>There are also several example use-cases,
such as this function
for fluid typography:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@custom-function</span> <span class="token function">--fluid-ratio</span><span class="token punctuation">(</span><br /> --min-width<span class="token punctuation">,</span><br /> --max-width<br /><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span><br /> 0%<span class="token punctuation">,</span><br /> 100% * <span class="token punctuation">(</span>100vw - <span class="token function">var</span><span class="token punctuation">(</span>--min-width<span class="token punctuation">)</span><span class="token punctuation">)</span> / <span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--max-width<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--min-width<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> 100%<br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token function">--fluid-ratio</span><span class="token punctuation">(</span>375px<span class="token punctuation">,</span> 1920px<span class="token punctuation">)</span><span class="token punctuation">,</span> 1rem<span class="token punctuation">,</span> 1.25rem<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token function">--fluid-ratio</span><span class="token punctuation">(</span>375px<span class="token punctuation">,</span> 700px<span class="token punctuation">)</span><span class="token punctuation">,</span> 1rem<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<details data-alert="warn">
<summary>Unit division in math functions:</summary>
<div><p>In addition to the new syntax proposed here,
browsers would also need to implement
<a href="https://drafts.csswg.org/css-values/#calc-type-checking">unit-division in math functions</a>
for this use-case to work as<span class="widont"> </span>shown.</p>
</div>
</details>
<p>Or a function for
generating checkerboard background-images:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@custom-function</span> <span class="token function">--checkerboard</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><br /> 45deg<span class="token punctuation">,</span><br /> silver 25%<span class="token punctuation">,</span><br /> transparent 25%<span class="token punctuation">,</span><br /> transparent 75%<span class="token punctuation">,</span><br /> silver 75%<br /> <span class="token punctuation">)</span><br /> 0px 0px / <span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><br /> 45deg<span class="token punctuation">,</span><br /> silver 25%<span class="token punctuation">,</span><br /> transparent 25%<span class="token punctuation">,</span><br /> transparent 75%<span class="token punctuation">,</span><br /> silver 75%<br /> <span class="token punctuation">)</span><br /> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span> / 2<span class="token punctuation">)</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span> / 2<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--size<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.used</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">--checkerboard</span><span class="token punctuation">(</span>32px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>For these use-case,
custom functions could be a simple wrapper
for inserting parameters into
existing functions like <code>calc()</code>.
Tab Atkins has suggested a math-only version of this
would be simplest to implement.
While that might be a useful first-step,
it quickly falls short of the use-cases I’ve seen.
I would prefer to start with a more fully-featured approach,
and work backwards to an attainable level 1 implementation
if needed.</p>
<p>In addition to some bike-shedding of the syntax,
there are several more open questions in the thread:</p>
<ul>
<li>Can authors provide a fallback output
for invalid arguments?</li>
<li>Would it be helpful to include default parameter values
in the function definition?</li>
<li>Can function authors define internally-scoped custom properties?</li>
<li>Can authors use conditional at-rules
inside the function logic?</li>
<li>Can functions expose a parameter
that accepts bare calculations (without <code>calc()</code> syntax)
similar to <code>clamp()</code> etc?</li>
<li>Can functions perform recursive function calls?</li>
<li>Can functions be called with named
(rather than positional) arguments?</li>
</ul>
<p>I hope to expand on this proposal,
and explore some of those questions along the way.</p>
<!--
.##.....##.########.########....###...
.###...###.##..........##......##.##..
.####.####.##..........##.....##...##.
.##.###.##.######......##....##.....##
.##.....##.##..........##....#########
.##.....##.##..........##....##.....##
.##.....##.########....##....##.....##
-->
<h2 id="acknowledgments">Acknowledgments <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#acknowledgments">¶</a></h2>
<p>This proposal is based on
an <a href="https://github.com/w3c/csswg-drafts/issues/7490">existing discussion</a>
with input from:</p>
<ul>
<li>Johannes Odland</li>
<li>David Baron</li>
<li>Brian Kardell</li>
<li>Tab Atkins-Bittner</li>
<li>@jimmyfrasche</li>
<li>Brandon McConnell</li>
<li>Lea Verou</li>
</ul>
<p>I’ve also incorporated feedback
along the way from:</p>
<ul>
<li>Nicole Sullivan</li>
<li>Anders Hartvoll Ruud</li>
<li>Rune Lillesveen</li>
<li>Alan Stearns</li>
<li>Yehonatan Daniv</li>
<li>Emilio Cobos Álvarez</li>
<li>François Remy</li>
<li>Steinar H Gunderson</li>
<li>Matt Giuca</li>
</ul>
<h2 id="todo">Todo <a class="header-anchor" href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2024-02-11t202037000z/#todo">¶</a></h2>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1717661703">Defer mixin-nested selectors</a>
as <a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1723337386">potentially expensive</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1719603753">Clarify recursion limitations</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1720836487">Clarify static vs dynamic args</a></li>
</ul>
2023-12-14T00:00:00Z
https://css.oddbird.net/overflow/explainer/
New page: CSS Overflow - Broad Research
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/overflow/explainer/">
CSS Overflow - Broad Research »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/overflow/explainer/">
CSS Overflow - Broad Research »
</a>
</p><hr /><details data-alert="note" open="">
<summary>Note:</summary>
<div><p>This is a high level review
of issues in overflow,
and potential features to explore.
We’ll break out individual explainers
for specific feature<span class="widont"> </span>proposals.</p>
</div>
</details>
<h2 id="authors">Authors <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#authors">¶</a></h2>
<ul>
<li>Miriam Suzanne</li>
<li>Robert Flack</li>
<li>Nicole Sullivan</li>
</ul>
<h2 id="participate">Participate <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#participate">¶</a></h2>
<p>Anyone can comment on,
file issues against,
or contribute to this explainer
on GitHub:</p>
<ul>
<li><a href="https://github.com/oddbird/css-sandbox/blob/main/src/overflow/explainer.md">Explainer Document</a></li>
<li><a href="https://github.com/oddbird/css-sandbox/issues">Issue Tracker</a></li>
</ul>
<p>We also rely heavily
on some existing proposals
discussed in the CSS Working Group
issue tracker:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/1183">[css-grid] Flow multiple elements together into same grid area</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/4416">[css-grid] grid area as element</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/499">[css-grid] Decorative grid-cell pseudo-elements</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6017">[css-multicol-2][css-scroll-snap] Snapping to column boxes</a></li>
</ul>
<h2 id="introduction">Introduction <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#introduction">¶</a></h2>
<p>Managing layout and design on the web
requires careful control of ‘overflow’ –
allowing content to be hidden
when it’s not needed,
but accessibly discovered and revealed
when necessary.</p>
<p>There are many overlapping
user-interface design patterns
that can be used –
from standard continuous scrolling,
to scroll-snapping,
slideshows, carousels,
accordions, tabs,
and various hybrid approaches.
The lines between these patterns
are often somewhat blurry:</p>
<ul>
<li>CSS currently supports several overflow values,
but the only built-in overflow-access interface
is the continuous scrolling container.
That can be extended with features
such as scroll-animation,
scroll-behavior, scroll-snapping,
and so on.</li>
<li>At the other end of a spectrum,
most tab and accordion patterns
are highly structured & sectioned overflow.
Each section has an interactive label
that can be used to show or hide
the section contents (often one-at-a-time).</li>
<li>In-between those two extremes,
there are a wide range of patterns
that get referred to as ‘carousels’.
These are not limited to the (problematic)
infinite-auto-advancing home-page widgets,
but range from video-streaming
media selection,
to slide shows,
and e-commerce product-image viewers.</li>
</ul>
<p>There have been many attempts
over the years
to define new tab or carousel components –
but they are often held up by:</p>
<ul>
<li>The complexity of
use-case mixing and matching
patterns from scrollers, carousels,
tabs, and accordions</li>
<li>The need to work around many
underlying features
that are missing from the web platform</li>
<li>The fact that ‘overflow’
is a presentational concern
tightly coupled with device/media/size conditions
(and therefor part of CSS),
but also requires interactive controls
generally left to HTML components or JavaScript.</li>
</ul>
<h2 id="goals">Goals <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#goals">¶</a></h2>
<p>The goal of this explainer
is not to propose yet another component,
or provide a one-size-fits-all solution
for all overflowing UI –
but to consider some baseline improvements to CSS overflow
that could help authors
flesh out the details of each pattern more elegantly,
consistently, and accessibly.
This could also help form the scaffolding
for future components, as needed.</p>
<p>In some cases,
we’re able to propose a path forward –
and in other cases,
we simply document the problem
and some of the tradeoffs
with different approaches.</p>
<h3 id="overlapping-patterns">Overlapping patterns <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#overlapping-patterns">¶</a></h3>
<p>The patterns we’re looking at
fall along a continuum
of scrolled and paged overflow:</p>
<ul>
<li>On one end,
media-scroller carousels
(like the Netflix video selection interface)
are similar to horizontal overflow
with scroll-snapping,
and the occasional use of ‘next/previous’ navigation
that is sometimes linked to items
and sometimes to ‘pages’ of overflow.</li>
<li>Slide-show style carousels
often add small ‘scroll-markers’ (like dots)
that represent either
the individual items listed,
or the viewable pages
(if multiple items appear at once).</li>
<li>Product-image carousels will often
replace the dots with thumbnail image scroll-markers,
and only show one item per ‘page’.
In this case general mouse scrolling
(and visible scroll-bars)
are often removed in favor of tab controls,
while sometimes leaving gesture-based scrolling intact.</li>
<li>Tabs are similar,
but may often removing scroll-controls entirely.</li>
</ul>
<p>Rather than thinking of these
as distinct patterns,
we want to understand the underlying set of controls
that are combined to create
any given variation.
Primarily: paged overflow
and interactive scroll markers
in a variety of forms.</p>
<h3 id="overflow-changes-based-on-context">Overflow changes based on context <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#overflow-changes-based-on-context">¶</a></h3>
<p>While HTML, CSS, and JS
often have distinct and separate ‘concerns’ –
semantics, presentation, and interaction –
overflow falls into a middle ground
that can be hard to break apart.
Content that overflows on one device,
or at a particular screen size,
may not need to overflow in other situations.
That’s a presentational concern,
rather than a semantic one,
and often needs to be handled using media queries.</p>
<p>However,
semantics do play a clear role in
what patterns make sense
(the difference between a carousel and tabs
often comes down to how content is sectioned),
and different types of ‘overflow’ require
different interactions.
Any solutions provided here
have to account for changes in overflow
based on device/media/container context –
and help authors access the proper interactions
and accessible roles
for each type of overflow.</p>
<h2 id="non-goals">Non-goals <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#non-goals">¶</a></h2>
<h3 id="application-level-tabs">Application-Level Tabs <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#application-level-tabs">¶</a></h3>
<p>The ‘tabs’ interface pattern
has two common
and somewhat distinct use-cases:</p>
<ol>
<li>Managing display of sectioned content
within a single document</li>
<li>Managing display of multiple documents
within an application</li>
</ol>
<p>The former can be seen
as a form of ‘content overflow’
covered by by this proposal.
When searching within a document,
users would expect content in hidden sections
to be ‘discoverable’.
On the other hand,
application level tabs
(such as in a browser or text-editor)
represent distinct documents
rather than ‘overflow’.
Users would not expect a search in one document
to reveal content in another tab.</p>
<p>This proposal is specific to
handling content overflow presentation in HTML and CSS,
rather than providing a generic solution
to the ‘tabs’ interface design pattern.</p>
<h3 id="auto-advancing--cyclic-home-page-carousels">Auto-advancing & Cyclic Home Page Carousels <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#auto-advancing--cyclic-home-page-carousels">¶</a></h3>
<p>The term ‘carousel’
has a strong negative connotation
in web design circles,
thanks to the poor user-experience
of home page carousels
used to display an endless cycle of
‘featured’ content.</p>
<p>Readers rarely interact with this pattern,
and generally don’t see content
beyond the first page of the carousel,
unless forced by auto-advancement,
which causes a whole new list
of accessibility and usability issues.</p>
<p>While that makes us hesitant
to use the term ‘carousel’ here,
there are a range of much more essential use-cases
that fall under the term –
and it’s helpful to understand
the somewhat fluid and overlapping nature of
scroll/carousel/tab/accordion patterns.</p>
<h3 id="virtual-lists">Virtual lists <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#virtual-lists">¶</a></h3>
<p>Virtual lists are tightly related to overflow,
but outside the scope of this document.</p>
<h2 id="possible-solutions">Possible solutions <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#possible-solutions">¶</a></h2>
<p>Rather than proposing a single
complete solution to these fluid ux patterns,
we think it would be useful to address
the core HTML/CSS
layout and overflow issues
that make such an element difficult to implement.
This should provide a stepping-stone
for both authors and browser vendors
to explore and develop more narrowly defined
web-components or HTML elements
where they make sense.</p>
<p>There has already been
some significant progress
along these lines.
The <code>scroll-snap</code> properties,
scroll-linked animations,
and size container queries
all help authors better address overflow.
Below, we’ll explore some of the features
that are still missing or difficult
for authors to get right.</p>
<h3 id="carousel-example">Carousel example <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#carousel-example">¶</a></h3>
<p><img src="https://css.oddbird.net/overflow/carousel-boxes.svg" alt="Carousel boxes" /></p>
<p>As a strawman example,
a developer should be able to use the following HTML
to create the visualized carousel:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>carousel</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>carousel</span><span class="token punctuation">></span></span></code></pre>
<p>The visualized carousel
can be constructed
with the following style
dynamically generating all of the required components:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">carousel</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span><br /> <span class="token property">grid-template</span><span class="token punctuation">:</span><br /> <span class="token string">'previous scroller next'</span> 1fr<br /> <span class="token string">'. markers .'</span> auto<br /> / auto 1fr auto<span class="token punctuation">;</span><br /><br /> <span class="token selector">> li</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Flow into scroller grid-flow defined below */</span><br /> <span class="token property">grid-flow</span><span class="token punctuation">:</span> --scroller<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">&::grid-flow(--scroller)</span> <span class="token punctuation">{</span><br /> <span class="token property">grid-area</span><span class="token punctuation">:</span> scroller<span class="token punctuation">;</span><br /> <span class="token comment">/* Paginate overflow? */</span><br /> <span class="token property">overflow</span><span class="token punctuation">:</span> paginate<span class="token punctuation">;</span><br /> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<span class="token punctuation">;</span><br /><br /> <span class="token selector">&::page</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* style all pages */</span><br /> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span><br /><br /> <span class="token selector">&:not(:active)</span> <span class="token punctuation">{</span><br /> <span class="token property">interactivity</span><span class="token punctuation">:</span> inert<span class="token punctuation">;</span><br /> <span class="token property">opacity</span><span class="token punctuation">:</span> .5<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/* Create markers for each page */</span><br /> <span class="token selector">::marker</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Flow them into the markers grid-flow */</span><br /> <span class="token property">grid-flow</span><span class="token punctuation">:</span> --markers<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">&::grid-flow(--markers)</span> <span class="token punctuation">{</span><br /> <span class="token property">grid-area</span><span class="token punctuation">:</span> markers<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">&::next</span> <span class="token punctuation">{</span><br /> <span class="token property">grid-area</span><span class="token punctuation">:</span> next<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">&::previous</span> <span class="token punctuation">{</span><br /> <span class="token property">grid-area</span><span class="token punctuation">:</span> previous<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Rob has put together
<a href="https://flackr.github.io/web-demos/carousel/">a prototype of this feature</a>,
and will write a more detailed explainer
for the features required.</p>
<h3 id="paged-overflow-in-the-browser">Paged overflow, in the browser <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#paged-overflow-in-the-browser">¶</a></h3>
<p>Simple carousels (or ‘media-scrollers’)
are often built with scrollable overflow
and scroll-snap targets.
What makes it a ‘carousel’ is the addition
of ‘paged’ navigation options:</p>
<ul>
<li>next/previous page arrows</li>
<li>page markers (often dots)
to show the number of pages
and current active position</li>
</ul>
<p>Paged overflow isn’t a new idea in CSS.
While implementations have not always been
complete or consistent,
paged overflow is already well-defined for print,
and has a number of
<a href="https://rachelandrew.co.uk/archives/2020/04/07/making-things-better/">other use-cases</a>
if it were available in the browser.</p>
<p>Establishing paged overflow on an element
would generate <code>::page</code> pseudo-element
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Fragmentainer">fragmentainers</a>
for descendant elements & content to flow through.</p>
<ul>
<li>pages are created as-needed
based on the amount of content,
and the size available to each page.</li>
<li>by default, a page could be the size
of the containing box (not including overflow),
so that scrolling the view 100%
would bring a new ‘page’ fully into view.</li>
<li>Pages could be resized,
which would impact how much content
fits in a given page,
how many pages are visible without scrolling,
and how many pages are needed
for the flow of content.</li>
<li>Individual pages could be targeted
using a syntax similar to <code>nth-child</code></li>
</ul>
<p>It’s not immediately clear
what syntax would be best
for invoking this sort of paged overflow.
While this behavior is related to both <code>overflow</code>
and (in some ways) <code>display</code>,
neither property seems like a particularly good fit.</p>
<p>Paged use-cases (e.g. carousels)
might involve scrolling between pages,
while others (e.g. multicol wrapping) may not.
So pagination is not necessarily alternative to scrolled overflow.
Even if paged overflow had an <code>auto</code>-like scroll behavior,
to allow scrolling and non-scrolling pages,
single-axis paged overflow(<code>-x</code>/<code>-y</code>) doesn’t make much sense.</p>
<p>Authors will also need a mechanism
for handling the layout of
elements within pages
(and the layout of the pages themselves) –
both of which require display values.
It’s possible the pagination and layout controls
could be combined in a single property
(e.g. <code>display: paged grid</code>)
if it makes sense for them to cascade together.
Since display is a shorthand for
inside and outside values,
pagination would either need to be added
to one of those properties
or a third new display sub-property.</p>
<h3 id="styling-paged-overflow">Styling paged overflow <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#styling-paged-overflow">¶</a></h3>
<p>As mentioned above,
authors will need a way to provide
<code>display</code> values for both</p>
<ol>
<li>the layout of <code>::page</code> elements themselves</li>
<li>the layout of child contents
flowing through those pages</li>
</ol>
<p>I would expect the overflowing/paginated parent element
to handle the layout of pages,
in which case
a new (<code>::page-contents</code>?) could be used
for the layout of content flowing through pages
(or vice versa).
An additional wrapping pseudo-element
like this might only support a limited subset of
CSS properties or pseudo-classes.</p>
<p>In some cases,
an author would style all <code>::page</code> elements,
but it would often be useful
to target specific pages
with e.g. <code>::page:nth-child(even)</code>
(or <code>::page(even)</code>).
However,
there might be recursion issues
with other combinations,
such as <code>::page:nth-last-child(even)</code>
or <code>::page:focus-within</code>,
if the styles applied
could change the number of pages
or placement of content in those pages.</p>
<p>Authors will often want to style the active page and marker.
Authors could use <code>:snapped</code> from <a href="https://drafts.csswg.org/css-scroll-snap-2/#snapped">css-scroll-snap-2</a> in combination with snapped pages. E.g.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">::page</span> <span class="token punctuation">{</span><br /> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">::page:snapped-inline::marker</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Highlight active marker. */</span><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid blue<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<details data-alert="note" open="">
<summary>Note:</summary>
<div><p>Could these pseudo-classes be used for paged
(e.g. print) media as<span class="widont"> </span>well?</p>
</div>
</details>
<h3 id="page-navigation--pagination">Page navigation / pagination <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#page-navigation--pagination">¶</a></h3>
<p>One of the design patterns
often associated with a ‘carousel’ component
is the use of ‘dots’ for
page-based navigation.
Rather than (or in addition to)
a continuous scrollbar,
readers can select the page
they want to move to.
These ‘scroll markers’ or ‘pagination markers’
are much like list markers –
often taking the form of dots/bullets,
but occasionally styled as counters,
or even thumbnails and other content.</p>
<p>Sometimes those page-access markers
represent individual items in a list.
This per-item marking
has a large overlap with ‘tabs’ –
a line that blurs in
‘product-image’ carousels for example.
Where’s the line between
a carousel-marker with an image thumbnail,
and a section tab with a text label?</p>
<p>In general,
item-based markers
can be provided by authors in the markup,
along with the items they mark.
In that case,
the main concerns are:</p>
<ul>
<li>Keeping the markers
inline with the items they mark,
vs creating a distinct table-of-contents
before/after the flow of items/pages</li>
<li>Managing the link between markers
and the content they mark</li>
<li>Maintaining the current state
of active content and markers</li>
</ul>
<p>We’ll explore
some of those issues more below.</p>
<p>However, many scroll markers represent
the abstract ‘pages’ generated by
a flow of content –
where the number of markers may change
based on the size of a container –
or arbitrary scroll-snap points
that combine items and pages.
In those situations,
a marker would need to be generated
either using JavaScript,
or (ideally) by the browser.</p>
<p>Una Kravets and Adam Argyle
have explored this in an early draft
<a href="https://github.com/argyleink/ScrollSnapExplainers/tree/main/css-scroll-marker"><code>::scroll-marker</code> explainer</a>.
In their proposal,
markers can be generated
by setting a <code>scroll-display</code> property
to one of <code>bar</code> (the default),
<code>auto</code>, or <code>marker</code>
on the scrolling parent.
This leaves a number of interesting questions
open for consideration:</p>
<ul>
<li>Would we need a way to request markers generated
per-item, per-page, or per-snap-target?
Can those options be combined?</li>
<li>Can the <code>scroll-display</code>
can be set differently for inline/block axis?</li>
<li>Can values be combined to show both
a continuous-scroll bar and also scroll markers?</li>
<li>Do previous/next arrows fall into a similar category?
While they are simpler for authors to provide in-markup,
they require a fair amount of state-management
to get right.</li>
</ul>
<h4 id="interactive-state-managing-generated-content">Interactive, state-managing, generated content? <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#interactive-state-managing-generated-content">¶</a></h4>
<p>One of the most difficult aspects
in building web carousels, slideshows, and tabs
is proper ‘wiring’ for the interactive navigation
(dots, tabs, prev/next) –
with proper accessibility and keyboard interaction,
along with scroll-position and active-state management.
This is especially true
when the ‘targets’ of that navigation
are based on fluid overflow
rather than specific DOM elements.</p>
<p>This is the primary reason
that authors would prefer a
built-in web platform solution,
but it also raises big questions
about using ‘generated content’
for interactive controls.</p>
<p>The generated markers
would also need to track and expose
the current active state -
providing a way to style the active marker.</p>
<h4 id="next--previous-buttons">Next / previous buttons <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#next--previous-buttons">¶</a></h4>
<p>Many ‘carousel’ components offer next and previous buttons.
These could be declared explicitly in HTML,
or constructed as interactive pseudo-elements (e.g. <code>::next</code>/<code>::previous</code>).
The behavior of these could be defined in many ways:</p>
<ul>
<li>Defined by Javascript handlers.
The amount to scroll can be calculated,
rely on <a href="https://www.w3.org/TR/css-scroll-snap-1/#choosing">css-scroll-snap-1 6.2</a> to choose the next snap point from a directional scroll (e.g. <code>scrollBy({left: 1})</code>),
or use the proposed <code>scrollTo({left: 'snap-next'})</code> <a href="https://github.com/argyleink/ScrollSnapExplainers/tree/main/js-scrollToOptions_Snap-Additions">explainer</a>.</li>
<li>Defined via page anchors (e.g. <code><a href="#page2"></code>).
This requires the author to create next / previous links for every page,
which are only visible on that page,
and to explicitly paginate their content independent of screen size.</li>
<li>Implicit, by using new pseudo-elements or attributes (e.g. <code><button type="next"></code>).</li>
</ul>
<h3 id="per-item-markers--tabs">Per-item markers & tabs <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#per-item-markers--tabs">¶</a></h3>
<p>In more tab-like cases,
where a predictable number of markers are needed –
e.g. one marker per list-item –
they could be provided in markup.</p>
<p>This has several advantages,
since it gives authors more control,
and doesn’t rely on interactive pseudo-elements.
However, it still comes with
it’s own set of issues:</p>
<ul>
<li>Do we need a new element or attribute for assigning
scroll-marker behavior?
Or do authors have to do
all the interaction/state-management manually?</li>
<li>Is there a way for authors to keep the marker markup inline,
and still ‘hoist’ the element into a parent grid context?</li>
</ul>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>tabs</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">marker</span><span class="token punctuation">></span></span>tab label<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>panel<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>tab panel<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /> …<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>tabs</span><span class="token punctuation">></span></span></code></pre>
<h3 id="layout-of-scroll-markers--tabs">Layout of scroll-markers / tabs <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#layout-of-scroll-markers--tabs">¶</a></h3>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/1183">[css-grid] Flow multiple elements together into same grid area</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/4416">[css-grid] grid area as element</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/499">[css-grid] Decorative grid-cell pseudo-elements</a></li>
</ul>
<h3 id="accessibility">Accessibility <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#accessibility">¶</a></h3>
<h4 id="exclude-offscreen-content">Exclude offscreen content <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#exclude-offscreen-content">¶</a></h4>
<p>It is common practice for carousels to only include content on the active screen
in the accessibility tree and in tab order.
Other screens are accessed via buttons or links.
In the <a href="https://www.w3.org/WAI/tutorials/carousels/full-code/">WAI tutorial</a>,
this is accomplished by setting the <code>aria-hidden</code> attribute from Javascript.
A pure declarative solution could be to allow setting inertness via CSS. E.g.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">::page:not(:active)</span> <span class="token punctuation">{</span><br /> <span class="token property">interactivity</span><span class="token punctuation">:</span> inert<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>See <a href="https://github.com/w3c/csswg-drafts/issues/7021">[css-ui] Should inertness be exposed as CSS property?</a>.</p>
<h4 id="interactive-pseudo-elements">Interactive pseudo-elements <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#interactive-pseudo-elements">¶</a></h4>
<p>If we have interactive pseudo elements they will need appropriate roles.
These could be fixed for the pseudo element type (e.g. next / previous are buttons).</p>
<h2 id="key-scenarios">Key scenarios <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#key-scenarios">¶</a></h2>
<p>[TBD]</p>
<h2 id="detailed-design-discussion--alternatives">Detailed design discussion & alternatives <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#detailed-design-discussion--alternatives">¶</a></h2>
<p>[TBD]</p>
<h2 id="prior-art--context">Prior Art & Context <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#prior-art--context">¶</a></h2>
<ul>
<li><a href="https://daverupert.com/2021/10/native-html-tabs/">spicy-sections</a>
from OpenUI</li>
<li><a href="https://github.com/argyleink/ScrollSnapExplainers/tree/main/css-scroll-marker"><code>::scroll-marker</code> proposal</a> from Una Kravets & Adam Argyle</li>
</ul>
<h2 id="stakeholder-feedback--opposition">Stakeholder Feedback / Opposition <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#stakeholder-feedback--opposition">¶</a></h2>
<p>[TBD]</p>
<h2 id="references--acknowledgements">References & Acknowledgements <a class="header-anchor" href="https://css.oddbird.net/overflow/explainer/#references--acknowledgements">¶</a></h2>
<p>Much of this work is based on
the research and proposals
compiled by others:</p>
<ul>
<li>Rachel Andrew</li>
<li>Una Kravets</li>
<li>Adam Argyle</li>
<li>Brian Kardell,
Dave Rupert,
Jon Neal,
Sarah Higley,
Scott O’Hara,
and others
in the OpenUI Community Group.</li>
</ul>
<details data-alert="note" open="">
<summary>Note:</summary>
<div><ul>
<li>document snapping approach</li>
<li>scroll markers are tabs<span class="widont"> </span>(aria)</li>
<li>focus groups</li>
<li>gestures vs mouse (mouse<span class="widont"> </span>fling?)</li>
</ul>
</div>
</details>
2023-12-01T21:40:11Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-12-01t214011000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Updates to parameter syntax and variable<span class="widont"> </span>scope</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Updates to parameter syntax and variable<span class="widont"> </span>scope</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-12-01t214011000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-12-01t214011000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-09-13T08:03:41Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-13t080341000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Provide acknowledgments</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Provide acknowledgments</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-13t080341000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-13t080341000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-09-12T17:40:02Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t174002000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Document potential built-in keyframes<span class="widont"> </span>mixin</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Document potential built-in keyframes<span class="widont"> </span>mixin</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t174002000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t174002000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-09-12T16:56:01Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t165601000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Clarifications and updates based on initial review and informal <span class="caps">TPAC</span><span class="widont"> </span>discussions</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Clarifications and updates based on initial review and informal <span class="caps">TPAC</span><span class="widont"> </span>discussions</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t165601000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-09-12t165601000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-08-22T19:11:04Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t191104000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Use semi-colon as argument delimiter, and add named<span class="widont"> </span>arguments</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Use semi-colon as argument delimiter, and add named<span class="widont"> </span>arguments</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t191104000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t191104000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-08-22T16:20:26Z
https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t162026000z/
Changes to: CSS Mixins & Functions Explainer
<blockquote>
<p>Document issues with function<span class="widont"> </span>fallbacks</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>Document issues with function<span class="widont"> </span>fallbacks</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t162026000z/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/changelog/sasslike/mixins-functions/2023-08-22t162026000z/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-08-21T00:00:00Z
https://css.oddbird.net/sasslike/mixins-functions/
New page: CSS Mixins & Functions Explainer
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/mixins-functions/">
CSS Mixins & Functions Explainer »
</a>
</p><hr /><p>This page has more recent changes available. For more details, see:</p>
<ul>
<li>The <a href="https://css.oddbird.net/sasslike/mixins-functions/#changelog-feed">current status on css.oddbird.net</a></li>
<li>The <a href="https://css.oddbird.net/sasslike/mixins-functions/#changelog-feed">log of major changes</a></li>
<li>The <a href="https://github.com/oddbird/css-sandbox/commits/main/src/feed.njk">github commit history</a></li>
</ul>
2023-06-22T00:00:00Z
https://css.oddbird.net/sasslike/
New page: Sass Features in CSS
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/">
Sass Features in CSS »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/sasslike/">
Sass Features in CSS »
</a>
</p><hr /><p>Over the last decade,
several features have made the move
from Sass (and other pre-processors) to CSS –
most notably ‘variables’
(as custom properties),
and now ‘nesting’.</p>
<p>Here we consider
some of the other Sass structures,
and what advantages we would see
from moving them into CSS.</p>
<p>As a useful reference-point,
the HTTP Archive
has done some
<a href="https://github.com/w3c/csswg-drafts/issues/5798">basic tracking of Sass usage</a>
across sites that provide
public Sass sourcemaps.</p>
2023-04-17T00:00:00Z
https://css.oddbird.net/overflow/
New page: CSS Overflow Extensions
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/overflow/">
CSS Overflow Extensions »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/overflow/">
CSS Overflow Extensions »
</a>
</p><hr /><p>Managing content ‘overflow’ effectively on the web
requires a careful balance
of semantics, presentation,
and interactions.
While existing scroll/snapping
and disclosure widgets
provide a partial solution,
authors often have to cobble together
tabs, carousels, and accordions
from an incomplete set of tools.</p>
<p>Rather than developing a proposal
to handle one of these patterns,
I’m interested in what tools are needed
to make the existing patterns easier to build
effectively and accessibly.</p>
2023-03-21T00:00:00Z
https://css.oddbird.net/scope/nesting/
New page: Nesting, Scoping, and Proximity (a FAQ)
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/scope/nesting/">
Nesting, Scoping, and Proximity (a FAQ) »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/scope/nesting/">
Nesting, Scoping, and Proximity (a FAQ) »
</a>
</p><hr /><h2 id="do-we-really-need-both-scoping-and-nesting">Do we really need both scoping and nesting? <a class="header-anchor" href="https://css.oddbird.net/scope/nesting/#do-we-really-need-both-scoping-and-nesting">¶</a></h2>
<p>Scope and nesting have some surface similarities,
and nesting <em>can be used</em>
(and has often been the only available tool)
to express scope-like relationships.
However, they diverge significantly from there.</p>
<p>At it’s core,
the <em>nesting</em> feature is
a semantically meaningless syntax sugar
to help authors express
any compound or complex selector
in a more terse and elegant way.
While the resulting selector
may involve ancestor/descendant relationships
(DOM nesting),
that is only one use-case among many others.
The fact of one selector fragment
being ‘nested’ inside another
does not imply any particular relationship
in the final selector:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.parent</span> <span class="token punctuation">{</span><br /> <span class="token selector">.child:has(~ &)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* .child:has(~ .parent) */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Scoping</em>, on the other hand,
is a semantic tool used to express
a specific relationship
between two or three matched elements.</p>
<ul>
<li>The scope ‘root’ element</li>
<li>Any optional scope ‘boundary’ elements
(which are only matched as descendants of the ‘root’)</li>
<li>Any scoped ‘subject’ selectors,
which only match the scope root or it’s descendants,
so long as there are no intervening ‘boundary’ elements</li>
</ul>
<p>These are three distinct selectors,
matching distinct elements in the DOM.
The ‘root’ and ‘boundary’ together are used
to establish a tree-fragment ‘scope’ –
and then subjects are restricted to match elements
inside that given fragment:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.parent<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.child:has(~ :scope)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* not possible to target subjects outside the scope */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Even where there is overlap
(when nesting is used to express
ancestor/descendant relationships)
this semantic distinction is important.</p>
<ul>
<li>The result of <em>nesting</em> is
a single selector list</li>
<li>The result of <em>scoping</em> is
two or three distinct selector lists</li>
</ul>
<p>This also plays out in the difference
between <code>&</code> or <code>:scope</code>
as ways of referencing context
in a wrapped selector.
The <code>&</code> selector (from nesting)
is a simple placeholder that can be replaced
by the wrapping selector fragment.
That fragment may match multiple elements</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.context</span> <span class="token punctuation">{</span><br /> <span class="token selector">& + & > &</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* .context + .context > .context */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>The scope selector,
instead,
references a single element:
<em>the root of the current scope</em>.
That element can’t have a relationship to itself,
so the selector above would not make sense
in a scoped context.</p>
<p>In order to merge the features,
we would need to choose one behavior or the other.
So instead, we’ve done what we can to
<em>differentiate</em> where they overlap:</p>
<ul>
<li>Nesting generates a single selector
with a single specificity,
while scoping keeps the selectors distinct
and does not apply the root or boundary specificity
to the scoped selectors inside</li>
<li>Even when nesting inside an <code>@scope</code> rule,
the <code>&</code>/<code>:scope</code> selectors maintain their distinct behaviors.</li>
</ul>
<h2 id="should-scope-be-an-at-rule-or-a-selector-syntax">Should scope be an at-rule or a selector syntax? <a class="header-anchor" href="https://css.oddbird.net/scope/nesting/#should-scope-be-an-at-rule-or-a-selector-syntax">¶</a></h2>
<p>This need for matching
two or three distinct elements
to generate a scope –
and then matching additional selectors inside that scope –
makes it difficult to represent ‘scope’
in a selector syntax.
How do you keep the three selectors
distinct from each other?</p>
<p>Since a scope includes everything from a specific matched root
until boundaries inside that root,
the scope ‘root’ and ‘boundary’ selectors
need to be explicitly associated with each other.
At first, we tried capturing that in parenthesis,
with a new <code>/</code> divider between the scope
start and end selectors:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">(.root / .boundaries) .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span></code></pre>
<p>In the simplest case, this makes sense.
But the fact that this syntax lives mid-selector
adds potential for a lot of confusion.
Each part of that selector can extend out
to a full list of complex selectors –
and also arbitrarily nested inside a selector list.</p>
<p>Are additional prefixed selectors
applied to the scope or the subject or both?</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.context ~ (.root / .boundaries) .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* which of these do we mean? or something else? */</span><br /><span class="token selector">(.context ~ .root / .context ~ .boundaries) .context ~ .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span><br /><span class="token selector">(.context ~ .root / .boundaries) .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span><br /><span class="token selector">(.root / .boundaries) .context ~ :scope .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span></code></pre>
<p>Which raises the question,
what does <code>:scope</code> refer to in the selector above?
Does it take meaning from the broader context outside the selector,
or does it refer to the scope root defined mid-selector?</p>
<p>If we nest scopes, how does that work?</p>
<pre class="language-css"><code class="language-css"><span class="token selector">((.outer / .scope) :scope .root / .boundaries) :scope > .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span><br /><span class="token selector">(.outer / .scope) :scope (.root / .boundaries) :scope > .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* … */</span> <span class="token punctuation">}</span></code></pre>
<p>Also, the scope part of the selector
doesn’t match any one element, but an entire tree fragment
that the subject has to be within.
Either we would need to disallow scopes as the right-most subject,
or imply a <code>*</code> selector at the end.</p>
<p>All of these are solvable problems
(and some can even be recreated with nesting the at-rule) –
but in general the at-rule helps
by keeping the three selectors distinct:</p>
<ul>
<li>The at-rule does not accept directly-nested declarations,
so it can never be the subject</li>
<li>There is one clear syntax for nesting scopes,
by putting one at-rule inside another</li>
<li>Each at-rule updates the meaning of <code>:scope</code>
for selectors inside the rule,
so it never has multiple meanings in a single context.</li>
<li>There is no way for a scope rule to end up inside pseudo-selectors,
so we don’t have to explicitly disallow that either.</li>
</ul>
<p>While it would be possible to define all the same behaviors
in a selector syntax,
it would require a whole list of special ‘limitations’
for authors to memorize –
limitations that are already implicit as part of an at-rule syntax.</p>
<p>A selector syntax is also harder to extend in the future,
if we want sibling scopes, or inclusive vs exclusive boundaries –
all of that detail has to be handled mid-selector,
either relying on ascii art, or some other new syntax.
In an at-rule, we can more simply provide keywords in a prelude.</p>
<p>But maybe even more importantly,
<strong>the purpose of scope is always to be a wrapper</strong>.
What makes selector syntax powerful
is the flexibility of combining different elements
in complex ways to create new matching behavior.
But with scope, we always want:</p>
<ul>
<li>to define a ‘scope’ sub-tree that will limit our matches</li>
<li>select specific elements in that ‘scope’ tree</li>
</ul>
<p>That’s a semantic intent clearly expressed
by nesting selectors inside a scope rule.
Removing the at-rule doesn’t remove the intent,
but only makes it more implicit –
and increases the chance for author mistakes.</p>
<p>(The proposed <code>>></code>/<code>~~</code> combinators have raised similar issues,
which can only be resolved by parsing out
distinct subject elements from a single selector –
and can get into similarly meaningless situations.
While that’s possible to define,
I’m not sure that the results are clear,
or have a distinct use-case apart from being
potential syntax sugar.)</p>
<h2 id="why-combine-lower-boundaries-and-proximity-in-a-single-feature">Why combine lower boundaries and proximity in a single feature? <a class="header-anchor" href="https://css.oddbird.net/scope/nesting/#why-combine-lower-boundaries-and-proximity-in-a-single-feature">¶</a></h2>
<p>Both of these behaviors
allow authors to establish limits
on the lower end of a component ‘scope’.
Lower boundaries provide a hard limit
(no elements match beyond this point)
while proximity provides a softer ‘giving way’
of priority to more narrowly targeted scopes.</p>
<p>We can see the similarity of purpose
in the fact that one of our core use-cases
could be expressed
either relying on lower boundaries
or proximity:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* avoid conflicts through lower boundaries */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-mode<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.dark-mode<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.dark-mode<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.light-mode<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> powderblue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* resolve conflicts through proximity */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-mode<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.dark-mode<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> powderblue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>The cascade-proximity heuristic
is a useful last-catch
for handling the cases where hard boundaries
would be too invasive –
but both are expressing the same concept
of an inner scope priority.</p>
<p>These two approaches also rely on the same
approach to element targeting.
The first step is to define the individual elements
that form ‘scope roots’.
then we can match the subject elements
in relation to those root elements
and determine:</p>
<ul>
<li>If there are intervening lower boundary elements</li>
<li>How many steps of the tree exist between the subject and root</li>
</ul>
<p>Neither of those can be determined
without separately matching
two or more distinct elements,
each with it’s own selectors:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span><start><span class="token punctuation">)</span> to <span class="token punctuation">(</span><end><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector"><subject></span> <span class="token punctuation">{</span> ... <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<ol>
<li><code><start></code>: the scope root</li>
<li><code><end></code> (optional): the lower boundaries</li>
<li><code><subject></code> the elements to style</li>
</ol>
<p>So in both ‘author intent’
(balancing inner/outer priorities)
and ‘technical approach’
(multiple selector subjects)
these two features are well aligned
to work as a pair.</p>
<h2 id="should-proximity-override-specificity">Should ‘proximity’ override ‘specificity’? <a class="header-anchor" href="https://css.oddbird.net/scope/nesting/#should-proximity-override-specificity">¶</a></h2>
<p>(We call it ‘weak’ proximity,
when it falls under specificity in the cascade –
or ‘strong’ proximity if it overrides specificity.)</p>
<p>In practice,
lower boundaries and a ‘weak’
cascade proximity
represent both ‘strong’ and ‘weak’ approaches
to avoiding scope conflict,
and giving priority to the inner-most rules.</p>
<p>Scope boundaries are explicitly defined,
and avoid cascading conflicts
by instead limiting the reach of a selector.
This is a ‘strong’ guard
against outer styles
influencing nested scopes.</p>
<p>In many cases as well,
different ‘types’ of scope
fall neatly into different layers.
For example, broader ‘color theme’ scopes
are likely to live in a lower layer
than narrow ‘component’ scopes.
That layering can be handled in CSS,
without relying on DOM proximity.</p>
<p>On the other end,
cascade proximity
is a <em>heuristic</em> that can help
in situations where more explicit
rules don’t make sense.
This situation primarily comes up
when two scopes have similar intent
(and are therefor in a similar layer,
with similar specificity).</p>
<p>In practice,
what we’ve found is that:</p>
<ul>
<li>Since proximity is a heuristic,
it is not reliable as a primary determining factor
between all scopes</li>
<li>Since proximity is determined by the DOM,
and specificity can be adjusted in CSS,
it’s useful to have the more ‘controllable’
heuristic take precedence</li>
<li>Since layers and scope both
help authors reduce specificity all-around,
there are more and more situations
where ‘code order’ is the determining factor in the cascade,
and proximity provides a better resolution in those cases</li>
</ul>
<p>Because of those considerations,
authors who have explored the current prototype –
including many who came in expecting to argue for
‘strong’ proximity –
have so far consistently preferred
the ‘weak’ approach.</p>
<p>While I’m sympathetic to the sense
that ‘specificity is unreliable as a heuristic’,
I think we need to be careful not to assume
that ‘therefor proximity would be more reliable’.
Both are heuristics that
can be useful as sorting mechanisms –
but neither one provides an air-tight mechanism
for author control.
And in practice, specificity provides more author control
over the final results.</p>
<p>The other counter-argument that’s come up is:
<em>if we don’t want it to be strong, do we need it at all</em>?
To me, this misses a few things:</p>
<ul>
<li>Relying on code order,
the weakest step of the cascade,
is <em>not at all an edge case</em>.
Many many cascade conflicts get to that final step
before being resolved.</li>
<li>CSS is a language built on semantic and declarative rules,
where <em>finesse</em> is often the best solution to a problem.
Giving a heuristic more power
does not make it more useful –
only more likely to override other considerations.</li>
</ul>
2022-11-21T00:00:00Z
https://css.oddbird.net/rwd/style/explainer/
New page: CSS Style Query Explainer
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/rwd/style/explainer/">
CSS Style Query Explainer »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/rwd/style/explainer/">
CSS Style Query Explainer »
</a>
</p><hr /><h2 id="authors">Authors <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#authors">¶</a></h2>
<p>Miriam Suzanne</p>
<h2 id="participate">Participate <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#participate">¶</a></h2>
<p>These features are already defined in the
<a href="https://drafts.csswg.org/css-contain-3/#style-container">CSS Containment Module Level 3 Working Draft</a>.</p>
<p>Initial CSSWG issues:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5989">What container features can be queried? #5989</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6396">Define a syntax for style-based container queries #6396</a></li>
</ul>
<p>Currently open CSSWG issues:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5624">Higher level custom properties that control multiple declarations #5624</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7413">Should style() queries allow !important flag? #7413</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7185">Move style queries of standard properties to level 4 #7185</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6966">Add ability to test for at-rule preludes #6966</a></li>
</ul>
<p>Deferred issues for level 4:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7068">Extend style query syntax? #7068</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6402">Define a syntax for state-based container queries #6402</a></li>
</ul>
<p>Related links:</p>
<ul>
<li><a href="https://github.com/w3ctag/design-reviews/issues/787">Request for TAG review</a></li>
<li><a href="https://una.im/style-queries/">Article & Use-Cases by Una Kravets</a></li>
</ul>
<h2 id="introduction">Introduction <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#introduction">¶</a></h2>
<p>In a world of design systems and custom components,
authors need a way to define simple parameters
that control multiple properties.
The current workaround
used by many web component authors
is to define custom attributes (often <code>data-*</code>)
that are passed in via HTML.</p>
<p>However, the <a href="https://w3ctag.github.io/webcomponents-design-guidelines/#:~:text=Don%E2%80%99t%20use%20custom%20attributes%20for%20styling">TAG guidelines</a>
state that custom attributes should not be used for styling,
and authors should rely on custom properties instead.
While it’s true in theory that custom properties would be
a better solution –
since they exist entirely in CSS,
and cascade the same as other style properties –
custom properties are currently limited to
carrying a single value.
It’s hard to achieve any more complex
impact on other properties,
beyond simple substitution.</p>
<p>Meanwhile, container queries
allow authors to ‘query’
some set of conditions on an ancestor element,
in the same ways that media queries
allow us to query various conditions
of the overall viewport, browser, and interface.</p>
<p>Also similar to media queries,
the majority of discussion
has historically focussed on
<a href="https://css.oddbird.net/rwd/query/explainer/">size-based queries</a> —
especially the width of the viewport or container.
But much like
device-interface and user-preference media queries,
there are a number of other powerful
‘container features’ that
would be useful to query.
We’ve categorized these roughly
into two types:</p>
<ol>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6396">Style features</a>
(already specified in
<a href="https://www.w3.org/TR/css-contain-3/#style-container">CSS Containment Level 3</a>)
allow querying
the <em>computed styles</em> of a container.</li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6402">State features</a>
would allow querying various aspects of
the container’s current state —
such as a <code>position:sticky</code> container
being currently ‘stuck’,
or an <code>overflow:auto</code> container
currently ‘overflowing’.
These feature would likely need to be defined
one-at-a-time, and require more research.</li>
</ol>
<p>This document is an explainer of possible
container query style features,
which would allow authors to use
custom properties (and existing properties)
to create higher-level
patterns and controls.</p>
<h2 id="goals">Goals <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#goals">¶</a></h2>
<p>The goal of this proposal
is to allow authors to define conditional rules
based on their container context –
and the cascaded/computed values
of container properties.
For example:</p>
<ul>
<li>Custom properties should be able
to have a <a href="https://github.com/w3c/csswg-drafts/issues/5624">broader impact on styles</a>,
rather than simply holding value fragments to be applied later.
By querying the value of a custom property,
authors can define that broader impact
directly in CSS.</li>
<li>Authors often define and apply
explicit selector hooks (classes and attributes) in the DOM,
in order to represent ‘current style context’
(such as an element having light or dark background).
It would be more direct to query those styles explicitly,
and keep all the logic in CSS.</li>
</ul>
<p>This proposal aims to give authors
those tools, such that the resulting conditional styles:</p>
<ul>
<li>Can apply to more than one property
without unnecessary repetition of the condition.</li>
<li>Don’t require an explicit inline fallback,
but can fallback to values defined outside the condition.</li>
</ul>
<p>Neither of those is possible to achieve
with inline conditional statements.</p>
<h2 id="non-goals">Non-goals <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#non-goals">¶</a></h2>
<p>In some use-cases,
authors only want to change a single property
based on an inherited style.
For example,
an author may want to un-italicize
the <code>em</code> element
when the context is already italic.
While that’s possible
using the container query syntax proposed here,
an <a href="https://github.com/w3c/csswg-drafts/issues/5009#issuecomment-626072319">inline conditional</a>
or <a href="https://drafts.csswg.org/css-values-5/#funcdef-toggle"><code>toggle()</code></a> function
is likely to provide
better ergonomics.</p>
<p>Ideally authors would have
both inline-value and at-rule level syntax available
for conditional statements –
and the two should integrate smoothly where possible.
However, this proposal
is entirely focused on the block level solution.</p>
<p>It’s also likely that we will want
to provide additional style query features,
particularly range-based style queries
(e.g. <code>(padding-inline > 1em)</code>),
or arbitrary style comparisons
(e.g. <code>(padding-inline > margin-inline)</code>
or <code>(1em > 2vw)</code>).
See <a href="https://github.com/w3c/csswg-drafts/issues/7068">issue #7068 in csswg-drafts</a>,
which has been deferred to level 4 of the specification.</p>
<p>In the meantime,
we think the basic computed-value match
provides a powerful first version of the functionality,
while avoiding some of the more difficult questions.
Equality comparisons for computed values
are already well-defined,
and give us a framework to build on.</p>
<h2 id="proposed-solutions">Proposed solutions <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#proposed-solutions">¶</a></h2>
<p>Container queries work
by defining <em>container</em> elements
on the page,
and then allowing their descendent elements
to apply <em>conditional styles</em>
based on their ancestor containers.</p>
<p>The initial release of this feature
(now available in Blink and WebKit browsers)
was primarily interested in
size-based or <em>dimensional</em> query features –
containing the size/layout of the container,
so that we can measure and respond to
it’s height, width, aspect ratio, or orientation.</p>
<p>At the same time
(and in <a href="https://drafts.csswg.org/css-contain-3/#style-container">the same specification</a>)
we left room for additional container/query types,
and defined the following syntax for
<em>container query style features</em>.</p>
<h3 id="querying-computed-styles-container-style">Querying computed styles: <code>@container style()</code> <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#querying-computed-styles-container-style">¶</a></h3>
<p>The container query syntax
relies on the <code>@container</code> rule:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <name>? <conditions></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* conditional styles */</span><br /><span class="token punctuation">}</span></code></pre>
<p>For dimensional queries,
that often looks very similar to a media query:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> layout <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 30em<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>However, the condition has to be resolved
individually for each element
matched by a selector (e.g. <code>.card</code>)
inside the query.
For each matched element,
we determine the proper container to query
by filtering ancestor elements for:</p>
<ul>
<li>A <code>container-name</code> that matches
the requested name, if specified
(in this case <code>layout</code>)</li>
<li>A <code>container-type</code> that is able to
handle the given conditions
(in this case <code>size</code> or <code>inline-size</code>)</li>
</ul>
<p>If a container is found,
the conditions are resolved against that container.
If multiple containers are found,
the ‘nearest’ relative container takes precedence.
If no container is found,
the query returns <code>unknown</code>
(similar to <code>false</code>, but also <code>false</code> when negated).</p>
<p>Style queries would use
the same underlying syntax and logic,
but differentiated by a <code>style()</code> function syntax,
which accepts any valid style declaration:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--highlight<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--highlight-text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--button</span><span class="token punctuation">:</span> pill<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span><br /> <span class="token property">padding-inline</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> colors <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a:any-link</span> <span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--link-on-dark<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Since we are comparing <em>computed values</em>,
we can use custom properties and relative units
on either side of the condition declaration –
and the replaced values will be used for comparison:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* --bg-dark is resolved on the container before comparing values */</span><br /><span class="token atrule"><span class="token rule">@container</span> theme <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-dark<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a:any-link</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> powderblue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Style conditions that query a shorthand property
are true if the computed values match
for each of its longhand properties.</p>
<h3 id="defining-style-containers-container-type-and-container-name">Defining style containers: <code>container-type</code> and <code>container-name</code> <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#defining-style-containers-container-type-and-container-name">¶</a></h3>
<p>Dimensional queries
require css <em>containment</em>
on the size, layout, and style
of the container
in order to prevent layout loops.
Containment is an invasive thing to apply broadly,
so it was important that authors
have careful control over what elements
are (or are not) size containers.</p>
<p>Style-based queries don’t have the same limitation.
There is already no way in CSS
for descendant styles to have an impact
on the computed styles of an ancestor.
So no containment is required,
and there are no invasive or unexpected side-effects
in establishing an element as a <em>style query container</em>.</p>
<p>Still, there are two important
(and somewhat distinct)
use-cases to consider when querying styles:</p>
<ul>
<li>For <em>inherited</em> properties,
the most relevant container is always
<em>the direct parent</em>.</li>
<li>For <em>non-inherited</em> properties,
the direct parent is often unreliable,
and it’s important to be explicit
about the container to query.</li>
</ul>
<p>This has been
<a href="https://css.oddbird.net/rwd/style/explainer/#default-container-types">discussed in great detail</a>
while determining the initial value
of <code>container-type</code>.</p>
<h4 id="default-containers-for-inherited-properties">Default containers for inherited properties <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#default-containers-for-inherited-properties">¶</a></h4>
<p>When querying inherited properties,
the most relevant ‘container’ context
will generally be the direct parent
of the querying element.</p>
<p>For example,
if we want to change the <code>em</code> styles
based on the surrounding context.
For most <code>em</code> elements, we would add italics,
but <em>when the context is italic already</em>
we may want to apply a background instead:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em</span> <span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--highlight-bg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--highlight-text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>In order for that <em>direct parent context</em>
to be available consistently,
we’ve defined that as the default:
<em>all elements are style containers</em>,
no matter what value is set in <code>container-type</code>.
Since there are no negative side-effects involved
with establishing style containers,
we think that’s the best path for author usability.</p>
<p>If we need a way for authors to turn that off in some cases,
we can consider adding a <code>container-type: none</code> value
to remove it from the list of style containers.
However, we haven’t seen any need for it
in the use-cases so far.
This
<a href="https://css.oddbird.net/rwd/style/explainer/#default-container-types">default container-type</a>
logic is discussed in more detail below.</p>
<h4 id="named-containers-for-non-inherited-properties">Named containers for non-inherited properties <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#named-containers-for-non-inherited-properties">¶</a></h4>
<p>With dimensional queries,
we can know that the nearest <code>size</code>/<code>inline-size</code> container
<em>has a relevant size to query</em>,
and with inherited properties
we can assume the direct parent
<em>has a relevant property/value to query</em>.
But we can’t reliably make any assumptions
about any container having a relevant value
for non-inherited properties</p>
<p>If we want to query the current padding
on a container,
or its background-color,
neither the direct parent
nor the nearest explicit container
would be reliable.
In this case,
authors should be much more explicit
about what containers
can provide relevant information.</p>
<p>That’s already possible with the existing
<code>container-name</code> property,
which accepts any number of optional/reusable names
for a container.
This allows authors to establish
custom container patterns
across multiple components:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.theme</span> <span class="token punctuation">{</span><br /> <span class="token property">container-name</span><span class="token punctuation">:</span> theme<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">.grid</span> <span class="token punctuation">{</span><br /> <span class="token property">container-name</span><span class="token punctuation">:</span> layout<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">.card</span> <span class="token punctuation">{</span><br /> <span class="token property">container-name</span><span class="token punctuation">:</span> card layout theme<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>By establishing container name conventions,
authors can ensure that a style query
is always targeting an appropriate container –
no matter their ancestor/descendant proximity,
or intervening (but irrelevant) containers.
To query the padding on a ‘card’ component,
always query a ‘card’ container.
To query the current color theme,
always query the nearest ‘theme’ container:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> card <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--small<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* nearest 'card' ancestor has --small padding */</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> theme <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-dark<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* nearest 'theme' ancestor has --bg-dark background-color */</span><br /><span class="token punctuation">}</span></code></pre>
<h2 id="key-scenarios">Key scenarios <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#key-scenarios">¶</a></h2>
<h3 id="setting-parameters-in-web-components">Setting parameters in web components <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#setting-parameters-in-web-components">¶</a></h3>
<p>One of the primary use-cases
mentioned by Lea Verou
in her <a href="https://github.com/w3c/csswg-drafts/issues/5624">request for higher level custom properties</a>,
is that web component authors
can expose parameters to
consumers of those components,
without needing to rely on custom attributes.</p>
<p>Given a <code>media-object</code> component
with the following HTML structure:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>article</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">part</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">part</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>article</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span></code></pre>
<p>We can use the <code>:host</code> element
as a container that accepts
various parameters:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:host</span> <span class="token punctuation">{</span><br /> <span class="token property">container</span><span class="token punctuation">:</span> media-host / inline-size<span class="token punctuation">;</span><br /> <span class="token property">--media-location</span><span class="token punctuation">:</span> before<span class="token punctuation">;</span><br /> <span class="token property">--media-style</span><span class="token punctuation">:</span> square<span class="token punctuation">;</span><br /> <span class="token property">--theme</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Elements inside the component
can query the parameters
set on the <code>media-host</code> container:</p>
<pre class="language-css"><code class="language-css"> <span class="token selector">article</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span><br /> <span class="token property">grid-template</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--default-template<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@container</span> media-host <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--media-location</span><span class="token punctuation">:</span> after<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">article</span> <span class="token punctuation">{</span><br /> <span class="token property">grid-template</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--reverse-template<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@container</span> media-host <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--theme</span><span class="token punctuation">:</span> fancy<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">article</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span><br /> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to bottom right<span class="token punctuation">,</span> #FFC0CBBB<span class="token punctuation">,</span> #EEEC<span class="token punctuation">,</span> #B0E0E6BB<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">conic-gradient</span><span class="token punctuation">(</span>red<span class="token punctuation">,</span> orange<span class="token punctuation">,</span> yellow<span class="token punctuation">,</span> green<span class="token punctuation">,</span> blue<span class="token punctuation">,</span> indigo<span class="token punctuation">,</span> violet<span class="token punctuation">,</span> red<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--media-color--dark<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> medium solid mediumvioletred<span class="token punctuation">;</span><br /> <span class="token property">border-top-left-radius</span><span class="token punctuation">:</span> 70% 60%<span class="token punctuation">;</span><br /> <span class="token property">border-top-right-radius</span><span class="token punctuation">:</span> 30% 40%<span class="token punctuation">;</span><br /> <span class="token property">border-bottom-right-radius</span><span class="token punctuation">:</span> 30% 60%<span class="token punctuation">;</span><br /> <span class="token property">border-bottom-left-radius</span><span class="token punctuation">:</span> 70% 40%<span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token atrule"><span class="token rule">@container</span> media-host <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--media-style</span><span class="token punctuation">:</span> round<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">[part='media']</span> <span class="token punctuation">{</span><br /> <span class="token property">border-radius</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span></code></pre>
<p>See the
<a href="https://codepen.io/miriamsuzanne/pen/abKVaoo?editors=1000">web component style query parameters demo</a>
on CodePen.</p>
<h3 id="contextual-configuration-without-custom-elements">Contextual configuration without custom elements <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#contextual-configuration-without-custom-elements">¶</a></h3>
<p>While custom elements
helpfully provide a wrapping element
that can be used as a container –
the same general approach can be used
to establish contextual parameters
with normal light-DOM elements.
We can do that either with more generally-applied
(or even ‘global’) configuration.</p>
<p>If we rely on inheritance
for the contextual parameters,
there’s no need to establish a container at all:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">main</span> <span class="token punctuation">{</span><br /> <span class="token property">--theme</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> #223<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> snow<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--theme</span><span class="token punctuation">:</span> blue<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.card</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> royalblue<span class="token punctuation">;</span><br /> <span class="token property">border-color</span><span class="token punctuation">:</span> navy<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">a:any-link</span> <span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> powderblue<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token selector">button</span> <span class="token punctuation">{</span><br /> <span class="token property">border-color</span><span class="token punctuation">:</span> navy<span class="token punctuation">;</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> dodgerblue<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Or we can add wrapper elements
by hand, when necessary.
Since this is required
for dimensional queries in relation
to grid and flexbox tracks,
the same wrappers can often be reused:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.card-container</span> <span class="token punctuation">{</span><br /> <span class="token property">container</span><span class="token punctuation">:</span> card / inline-size<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> card <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--theme</span><span class="token punctuation">:</span> blue<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* dark theme card styles */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> card <span class="token punctuation">(</span>inline-size > 30em<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* larger space card styles */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>There are various codepen demos
that explore use-cases along these lines:</p>
<ul>
<li><a href="https://codepen.io/una/pen/abGXjJZ?editors=1100">Style query test – card themes</a>
by Una Kravets</li>
<li><a href="https://codepen.io/miriamsuzanne/pen/abGBNNx">Style query button themes</a>
by Miriam Suzanne</li>
<li><a href="https://codepen.io/miriamsuzanne/pen/xxzXdJQ">Light/dark/invert themes with style queries</a></li>
</ul>
<h3 id="parameters-for-generated-content">Parameters for generated content <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#parameters-for-generated-content">¶</a></h3>
<p>Much like custom elements,
pseudo-elements
such as <code>::before</code> and <code>::after</code>
also come with a built-in container –
the element on which they are generated.
When using a pseudo-element to create
an ‘arrow’ on a tooltip,
we can use style queries to
set the style and position of the arrow:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.bubble</span> <span class="token punctuation">{</span><br /> <span class="token property">--arrow-position</span><span class="token punctuation">:</span> end end<span class="token punctuation">;</span><br /> <span class="token property">container</span><span class="token punctuation">:</span> bubble<span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> medium solid green<span class="token punctuation">;</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> 1em solid transparent<span class="token punctuation">;</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> end end<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">border-block-start-color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br /> <span class="token property">inset-block-start</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br /> <span class="token property">inset-inline-end</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> start end<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">border-block-start-color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br /> <span class="token property">inset-block-start</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br /> <span class="token property">inset-inline-start</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>We can also combine queries
to avoid repeated properties:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> start start<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> end start<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">border-block-end-color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br /> <span class="token property">inset-block-end</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> start end<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> end end<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">border-block-start-color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br /> <span class="token property">inset-block-start</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> start start<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> start end<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">inset-inline-start</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> bubble <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> end start<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--arrow-position</span><span class="token punctuation">:</span> end end<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.bubble::after</span> <span class="token punctuation">{</span><br /> <span class="token property">inset-inline-end</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>See the
<a href="https://codepen.io/miriamsuzanne/pen/vYjMjGd?editors=0100">queries with pseudo-classes demo</a>
on CodePen.</p>
<h3 id="simple-value-cycles">Simple value cycles <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#simple-value-cycles">¶</a></h3>
<p>One of the common use-cases
seems to come with the best alternative solution,
at least in it’s simplest form.
This is cycling one property-value
based on the value of the same property on the parent.</p>
<p>For example,
we can cycle the <code>font-style</code>
between <code>italic</code> and <code>normal</code> values
as we nest:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token property">font-style</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now our <code>em</code>, <code>i</code>, and <code>q</code> tags
will be italic by default,
but will revert to normal when nested
inside an italic parent –
for example an <code>em</code> inside a <code>q</code>.</p>
<p>However, there’s an
<a href="https://drafts.csswg.org/css-values-5/#funcdef-toggle">existing proposal & spec</a>
for handling this use-case
with a function,
currently called <code>toggle()</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token property">font-style</span><span class="token punctuation">:</span> <span class="token function">toggle</span><span class="token punctuation">(</span>italic<span class="token punctuation">,</span> normal<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="complex-value-adjustments">Complex value adjustments <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#complex-value-adjustments">¶</a></h3>
<p>In a case where the cycled styles
are limited to a single property,
the <code>toggle()</code> function is clearly a simpler solution.
But it has pretty strict limitations:</p>
<ul>
<li>One property cannot cycle
based on the inherited value of another property.</li>
<li>When multiple properties are involved,
each has to be handled individually.</li>
</ul>
<p>Instead of simply cycling between
italic and normal values,
we may want to give the nested version
a new background color,
or underline,
or other styles that make it stand out,
besides simply toggling the italics.</p>
<p>This is not possible with the functional approach,
but it becomes trivial with style queries:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> lightpink<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Queries also allow us to use
multiple property conditions:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">--color-mode</span><span class="token punctuation">:</span> light<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> lightpink<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Or apply the same query condition to multiple properties:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">em, i, q</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* clipped gradient text */</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--feature-gradient<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background-clip</span><span class="token punctuation">:</span> text<span class="token punctuation">;</span><br /> <span class="token property">box-decoration-break</span><span class="token punctuation">:</span> clone<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span><br /> <span class="token property">text-shadow</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>None of those variations are possible
using the proposed <code>toggle()</code> function.</p>
<h3 id="querying-non-inherited-properties">Querying non-inherited properties <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#querying-non-inherited-properties">¶</a></h3>
<p>There are various use-cases
that involve querying non-inherited properties.</p>
<h3 id="using-var-in-container-queries">Using <code>var()</code> in container queries <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#using-var-in-container-queries">¶</a></h3>
<p>Light/dark themes are a consistently useful
example of contextual styling
that may depend on multiple inputs.
A few examples might include
querying the background-color of a container
to determine if the context has a light or dark theme:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">main, aside</span> <span class="token punctuation">{</span><br /> <span class="token property">container</span><span class="token punctuation">:</span> theme<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">main</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-dark<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">aside</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-light<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> theme <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-dark<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* styles for our dark theme */</span><br /> <span class="token selector">a:any-link</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> powderblue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> theme <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-light<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* styles for our light theme */</span><br /> <span class="token selector">a:any-link</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> navy<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Given the code above,
links in the <code>aside</code> and <code>main</code> elements
will respond contextually
to the background colors established in each.</p>
<p>Another example might involve
lists and other ideally-outdented content
to query available padding
on any typesetting container that they are in:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span><br /> <span class="token property">container</span><span class="token punctuation">:</span> typeset<span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gap-small<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* This would ideally use a range query in the future */</span><br /><span class="token atrule"><span class="token rule">@container</span> typeset <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gap-large<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">ul</span> <span class="token punctuation">{</span><br /> <span class="token property">padding-inline-start</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>There are several CodePen demos
showing this sort of behavior,
although they currently require an extra
custom property,
since the Chromium prototype
doesn’t yet support queries on non-custom properties:</p>
<ul>
<li><a href="https://codepen.io/miriamsuzanne/pen/xxzXdJQ">Light/dark/invert themes with style queries</a></li>
<li><a href="https://codepen.io/miriamsuzanne/pen/LYrOgwM?editors=1100">List outdent with style queries</a></li>
</ul>
<h3 id="replacing-the-variable-space-toggle-hack">Replacing the ‘variable space toggle’ hack <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#replacing-the-variable-space-toggle-hack">¶</a></h3>
<p>The ‘variable space toggle’ hack
has gained some attention
as a workaround for simple true/false value conditions.
See, for example:</p>
<ul>
<li><a href="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/">https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/</a></li>
<li><a href="https://github.com/propjockey/css-sweeper#css-is-a-programming-language-thanks-to-the-space-toggle-trick">https://github.com/propjockey/css-sweeper#css-is-a-programming-language-thanks-to-the-space-toggle-trick</a></li>
<li><a href="https://css-tricks.com/the-css-custom-property-toggle-trick/">https://css-tricks.com/the-css-custom-property-toggle-trick/</a></li>
</ul>
<p>In many cases,
a container style query could be used
to provide similar functionality.
Some of these have already been demonstrated above.
However, the restriction against self-querying
can sometimes complicate those use-cases:</p>
<ul>
<li><a href="https://codepen.io/miriamsuzanne/pen/wvXyMZx">https://codepen.io/miriamsuzanne/pen/wvXyMZx</a></li>
</ul>
<p>If declaration-level conditional functions
are able to work around that limitation,
authors would have more options
for balancing the limitations of each approach.</p>
<h2 id="detailed-design-discussion--alternatives">Detailed design discussion & alternatives <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#detailed-design-discussion--alternatives">¶</a></h2>
<h3 id="alternatives-to-an-at-rule-syntax">Alternatives to an at-rule syntax <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#alternatives-to-an-at-rule-syntax">¶</a></h3>
<p>There have been several proposals
for using custom properties
as <a href="https://github.com/w3c/csswg-drafts/issues/5624">higher-level controls</a>.</p>
<h4 id="inline-conditional-functions">Inline conditional functions <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#inline-conditional-functions">¶</a></h4>
<p><a href="https://github.com/w3c/csswg-drafts/issues/5009#issuecomment-626072319">Inline conditional functions</a>
would provide a declaration-level control.
There are several proposals,
most notably Lea Verou’s
<a href="https://drafts.csswg.org/css-conditional-values-1/#if">inline <code>if()</code></a>
proposal,
which seem useful to pursue
in parallel to this rule-block level feature.</p>
<p>Inline functions
have some limitations that can only be addressed
at this higher level:</p>
<ul>
<li>When controlling multiple properties
based on a shared condition,
the inline approach becomes extremely repetitive.</li>
<li>When an at-rule or selector fails to match,
the declarations inside are not applied
and have no impact on the cascade –
allowing implicit fallbacks to be defined
elsewhere in the styles.
However, at the point where an inline condition
resolves to false,
the cascade has already completed.
Any fallbacks need to be defined inline as well.</li>
</ul>
<p>We don’t see these as competing approaches,
but features that would complement each other.</p>
<h4 id="global-constants">Global constants <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#global-constants">¶</a></h4>
<p>Allowing selectors to access
arbitrary custom properties
creates a difficult condition loop –
where selectors and declarations
both depend on the other to resolve first.
One way around that would be to add
<a href="https://github.com/w3c/csswg-drafts/issues/5624#issuecomment-766886708">global constants</a>
that can be defined at the root of a document
(including web components),
but not altered in selectors.
To quote from the proposal:</p>
<blockquote>
<p>These custom properties:</p>
<ul>
<li>Can only take <code><number></code> and <code><keyword></code> values
(syntax may be extended in the future).
They cannot contain <code>var()</code> references.</li>
<li>Will likely be syntactically different
rather than just a different option in <code>@property</code>/<code>CSS.registerProperty()</code>.
This way the difference is obvious,
and also since they are resolved so early,
they are not subject to IACVT and can “fail early”
like every other property. Exact syntax TBD.</li>
<li>Their values can be referenced with <code>var()</code>
just like regular custom properties.</li>
<li>Custom properties can reference them with <code>var()</code>,
even though the opposite is not true.</li>
</ul>
</blockquote>
<p>This would require
a secondary property-definition syntax,
and clear distinction for authors between
‘types’ of custom properties
with different cascading features.
It also severely limits the author’s ability
to <em>use the cascade</em> in establishing
the variables to begin with.
Since the values are global within a context,
authors would need to rely on web components specifically,
in order to establish nested contexts
with different ‘global’ parameters.</p>
<h4 id="conditional-pseudo-class">Conditional pseudo-class <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#conditional-pseudo-class">¶</a></h4>
<p>One of the more obvious syntax solutions
for a rule-block level condition
would be a functional pseudo-class.
However,
that introduces a dependency between
the selectors themselves
and the property-values
defined in the selector rule block.</p>
<p>Implementors are understandably
hesitant to add the multiple passes
required to resolve those conflicts –
but one workaround would be
<em>disallowing the pseudo-class on the subject compound</em>
(as <a href="https://github.com/w3c/csswg-drafts/issues/5624#issuecomment-806823667">suggested by Anders Hartvoll Rudd</a>):</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* Not valid */</span><br /><span class="token selector">.subject:const(--x:1)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* Valid */</span><br /><span class="token selector">.container:const(--x:1) .subject</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span></code></pre>
<p>This is a viable solution,
but it stands out to me as having
the same limitations as a container query approach
(for the same reasons) –
while providing a bit less clarity and flexibility.</p>
<p>While there are other pseudo-classes
that are only meaningful on specific elements,
I don’t know of existing cases
where a pseudo-class is <em>invalid</em>
in a specific selector position.
In this case,
the limitation is not obvious
in the syntax itself,
and could be difficult to teach or debug.
On the other hand,
an at-rule makes the distinction more clear:
a ‘container’ (target of the at-rule)
exists outside the ‘subject’ (target of the selector)
which will apply the conditional styles.</p>
<p>That has several advantages:</p>
<ul>
<li>Separate selectors can be used to
define containers and subjects,
rather than a single selector needing to target both</li>
<li>Query targeting can rely on a cascaded <code>container-name</code>,
rather than needing to re-assert
the selectors for all possible containers
in the conditional selector</li>
<li>Multiple selectors can be targeted
within a single condition</li>
</ul>
<p>All of those issues are possible to work around
using a nested syntax –
but that relies on authors
to provide clarity,
rather than supplying it directly
in the syntax provided.</p>
<h4 id="building-on-container-queries">Building on container queries <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#building-on-container-queries">¶</a></h4>
<p>Meanwhile,
the existing Container Query syntax
already provides
many of the requirements
for making style queries
on <em>all properties</em>
both functional and convenient,
without limiting them to global constants:</p>
<ul>
<li>The existing <code>@container</code> rule
is established as a way
of querying conditions on specific elements.
Querying the value of a property
is well within the scope of that functionality.</li>
<li>The initial <code>normal</code> value
for <code>container-type</code> simplifies the process for
querying the nearest parent styles,
especially useful for inherited properties.</li>
<li>The existing <code>container-name</code> syntax
allows explicit targeting of containers,
especially useful for non-inherited properties.</li>
<li>Container queries enforce a separation between
the elements being styled
and the ‘container’ being queried,
which avoids potential style loops.
This is the same limitation required
by a pseudo-class,
but the syntax does a better job ‘enforcing’
and clarifying the restriction.</li>
<li>Authors are familiar with conditional at-rules,
and can use existing methods for establishing
fallback behavior.
The addition of at-rule nesting
will allow a somewhat more ‘inline’ approach
when appropriate.</li>
<li>This approach does not require
any limitation on the type of property
being queried,
but would work with all existing
(custom and pre-defined) properties.</li>
</ul>
<p>The primary downside
is that elements are not able to query themselves.
This limitation is also unfortunate
in dimension-based queries –
and would likely limit pseudo-classes as well.
The workaround can require extra HTML
in some cases,
but that is less of an issue
in web components
where the <code>:host</code> can often act as a container
for the elements within:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:host</span> <span class="token punctuation">{</span> <span class="token property">container-name</span><span class="token punctuation">:</span> host<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> host <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--orientation</span><span class="token punctuation">:</span> landscape<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* conditions using custom property parameters on the host element */</span><br /><span class="token punctuation">}</span></code></pre>
<p>This syntax should also be extensible
in the future, to allow:</p>
<ul>
<li>Range queries, in addition to strict equality,
e.g. <code>(margin >= 1em)</code></li>
<li>Arbitrary expressions,
e.g. <code>(1vw > 1em)</code> or even <code>(margin > padding)</code></li>
</ul>
<p>Additionally, it would complement an inline <code>if()</code>,
so that both could rely on similar condition syntax,
and rules for comparison –
but the inline version would provide
a different set of tradeoffs:</p>
<ul>
<li>Single-property conditions (a shorthand for the simpler cases)</li>
<li><em>Invalid at computed value time</em> behavior,
rather than forced ancestor/descendant limitation</li>
<li>Property-aware resolution of values for
e.g. <code>%</code> units that have different meaning in different locations</li>
</ul>
<h3 id="default-container-types">Default container types <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#default-container-types">¶</a></h3>
<p>There have been several relevant discussions
about the proper <em>initial value</em>
for <code>container-type</code>,
specifically in relation to
the needs of style queries:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7202">#7202: Make container-type:auto the initial value?</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7066">#7066: Revisit decision to make style the default container-type</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/7402">#7402: Rename ‘none’ to ‘normal’</a></li>
</ul>
<p>In the end,
browsers have shipped an initial
<code>container-type</code> value
of <code>normal</code> –
which allows us to
expand the meaning of that
as we add new query types.</p>
<p>The current plan is for
all existing <code>container-types</code> –
<code>normal</code>, <code>size</code>, and <code>inline-size</code> –
to support style queries.
Without that feature,
it becomes much more difficult
to query inherited properties.
Authors would likely apply a <code>style</code> type universally,
and then need to be careful
not to override that value
when establishing size containers:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> style<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* Would accidentally remove the style type */</span><br /><span class="token selector">main</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<p>If we find that there are reasons
to <em>override</em> that
all-elements-are-style-containers
default behavior,
we can provide an explicit
<code>none</code> value.
At this point,
we haven’t found any clear need for it.</p>
<h2 id="stakeholder-feedback--opposition">Stakeholder Feedback / Opposition <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#stakeholder-feedback--opposition">¶</a></h2>
<ul>
<li>Chromium : Positive —
There is already a partial (custom properties only)
prototype implementation in v107+ behind the
‘experimental web platform features’ flag.</li>
<li>Gecko : <a href="https://github.com/mozilla/standards-positions/issues/686">No signals</a></li>
<li>Webkit : <a href="https://github.com/WebKit/standards-positions/issues/57">No signals</a></li>
</ul>
<h2 id="references--acknowledgements">References & acknowledgements <a class="header-anchor" href="https://css.oddbird.net/rwd/style/explainer/#references--acknowledgements">¶</a></h2>
<p>The CSS Containment spec is co-authored
by Tab Atkins and Florian Rivoal.
Elika Etimad was also involved
in specifying style queries.
Many of the goals and use-cases
are based on the work of Lea Verou
and others in the CSSWG-drafts Github issues,
along with Una Kravets and others linked above.</p>
<p>It has also been helpful
to have the Chromium prototype for experimentation,
with feedback from Nicole Sullivan,
and the browser engineers involved:
Rune Lillesveen, and Anders Hartvoll Ruud.</p>
2022-10-25T00:00:00Z
https://css.oddbird.net/rwd/style/
New page: CSS Style Queries
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/rwd/style/">
CSS Style Queries »
</a>
</p>
<blockquote>
<p>New page added</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/rwd/style/">
CSS Style Queries »
</a>
</p><hr /><p>Style queries are a subset of ‘container queries’,
but rather than querying conditions of the <em>container size</em>,
we can query the <em>computed value</em>
of any CSS properties on the container.</p>
2022-10-03T00:00:00Z
https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/
Changes to: CSS Scope Proposal & Explainer
<blockquote>
<p>Document scopes without selectors (thanks to <a href="https://twitter.com/dfabu">Dan Fabulich</a>)</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/scope/explainer/">
CSS Scope Proposal & Explainer »
</a>
</p>
<blockquote>
<p>Document scopes without selectors (thanks to <a href="https://twitter.com/dfabu">Dan Fabulich</a>)</p>
</blockquote>
<p>
<a href="https://css.oddbird.net/scope/explainer/">
CSS Scope Proposal & Explainer »
</a>
</p><hr /><h2 id="authors">Authors <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#authors">¶</a></h2>
<p>Miriam Suzanne</p>
<h2 id="participate">Participate <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#participate">¶</a></h2>
<p>Please leave any feedback on the CSSWG issues for this proposal:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5809">Proposal for light-dom scoping/namespacing</a></li>
<li><a href="https://github.com/w3ctag/design-reviews/issues/593">Request for TAG review</a>
(note that the explainer has been updated in response to feedback)</li>
<li><a href="https://drafts.csswg.org/css-cascade-6/#scoped-styles">CSS Cascade & Inheritance Module level 6</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues?q=is:open+is:issue+label:css-cascade-6">Issues labeled <code>css-cascade-6</code></a></li>
</ul>
<p>Typos or other document-specific issues
<a href="https://github.com/oddbird/css-sandbox/issues">can be reported in this repo</a>.</p>
<h2 id="introduction">Introduction <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#introduction">¶</a></h2>
<p>There are many overlapping
and sometimes contradictory features
that can live under the concept of “scope” in CSS –
but they divide roughly into two approaches:</p>
<ol>
<li>Total isolation of a component DOM subtree/fragment from the host page,
so that no selectors get in or out
unless explicitly requested.</li>
<li>Lighter-touch, style-driven namespacing,
and prioritization of “proximity”
when resolving the cascade.</li>
</ol>
<p>That has lead to a wide range of proposals over the years,
including a <a href="https://www.w3.org/TR/css-scoping-1/">scope specification</a>
that was never implemented.
Focus moved to Shadow-DOM,
which is mainly concerned with approach #1 – full isolation.
Meanwhile authors have attempted to handle approach #2
through convoluted naming conventions (like <a href="http://getbem.com/">BEM</a>)
and JS tooling
(such as <a href="https://github.com/css-modules/css-modules">CSS Modules</a>, <a href="https://styled-components.com/">Styled Components</a>, & <a href="https://vue-loader.vuejs.org/guide/scoped-css.html">Vue Scoped Styles</a>).</p>
<p>This document is proposing a native CSS approach
for what many authors are already doing
with those third-party tools & conventions.</p>
<h2 id="goals">Goals <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#goals">¶</a></h2>
<h3 id="the-namespace-problem">The namespace problem <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the-namespace-problem">¶</a></h3>
<p>All CSS Selectors are global,
matching against the entire DOM.
As projects grow,
or adapt a more modular “component-composition” approach,
it can be hard to track what names have been used,
and avoid conflicts.</p>
<p>To solve this,
authors rely on
convoluted naming conventions (BEM)
and JS tooling (CSS Modules & Scoped Styles)
to “isolate” selector matching inside a single “component”.</p>
<p>BEM helps authors by ensuring that only
component “blocks” need unique naming:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.media</span> <span class="token punctuation">{</span> <span class="token comment">/* block */</span> <span class="token punctuation">}</span><br /><span class="token selector">.tabs</span> <span class="token punctuation">{</span> <span class="token comment">/* block */</span> <span class="token punctuation">}</span></code></pre>
<p>Meanwhile,
any internal “elements” or “modifiers”
will be scoped to the block:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.media--reverse</span> <span class="token punctuation">{</span> <span class="token comment">/* modifier */</span> <span class="token punctuation">}</span><br /><span class="token selector">.media__img</span> <span class="token punctuation">{</span> <span class="token comment">/* element */</span> <span class="token punctuation">}</span><br /><span class="token selector">.media__text</span> <span class="token punctuation">{</span> <span class="token comment">/* element */</span> <span class="token punctuation">}</span><br /><br /><span class="token selector">.tabs--left</span> <span class="token punctuation">{</span> <span class="token comment">/* modifier */</span> <span class="token punctuation">}</span><br /><span class="token selector">.tabs__list</span> <span class="token punctuation">{</span> <span class="token comment">/* element */</span> <span class="token punctuation">}</span><br /><span class="token selector">.tabs__panel</span> <span class="token punctuation">{</span> <span class="token comment">/* element */</span> <span class="token punctuation">}</span></code></pre>
<h3 id="the-nearest-ancestor-proximity-problem">The nearest-ancestor “proximity” problem <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the-nearest-ancestor-proximity-problem">¶</a></h3>
<p>Ancestor selectors allow us to
filter the “scope” of nested selectors
to a sub-tree in the DOM:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* link colors for light and dark backgrounds */</span><br /><span class="token selector">.light-theme a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token selector">.dark-theme a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> plum<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<p>But problems show up quickly
when you start thinking of these as modular styles
that should nest in any arrangement.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>light-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>also plum???<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Our selectors appropriately have the same specificity,
but they are not weighted by
“proximity” to the element being styled.
Instead we fallback to source order,
and <code>.dark-theme</code> will always take precedence.</p>
<p>There is no selector/specificity solution
that accurately reflects what we want here –
with the “nearest ancestor” taking precedence.</p>
<p>This was one of the
<a href="https://www.slideshare.net/stubbornella/object-oriented-css/62-CSS_WISH_LIST">original issues highlighted by OOCSS</a>
in 2009.</p>
<h3 id="the-lower-boundary-or-ownership-problem-aka-donut-scope">The lower-boundary, or “ownership” problem (aka “donut scope”) <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the-lower-boundary-or-ownership-problem-aka-donut-scope">¶</a></h3>
<p>While “proximity” is loosely concerned with nesting styles,
the problem comes into more focus
with the concept of modular components –
which can be more complex.</p>
<p>To use BEM terminology,
Components are generally comprised of:</p>
<ul>
<li>An outer “block” or component wrapper</li>
<li>Inner “elements” that belong to that block explicitly</li>
<li>Occasional “donut holes” or “slots” where sub-components can be nested</li>
</ul>
<p>In html templating languages,
and JS frameworks,
this can be represented by an “include”
or “single file component”.</p>
<p>BEM attempts to convey this “ownership” in CSS:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* any title inside the component tree */</span><br /><span class="token selector">.component .title</span> <span class="token punctuation">{</span> <span class="token comment">/* too broad */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* only a title that is a direct child of the component */</span><br /><span class="token selector">.component > .title</span> <span class="token punctuation">{</span> <span class="token comment">/* too limiting of DOM structures */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* just the title of the component */</span><br /><span class="token selector">.component__title</span> <span class="token punctuation">{</span> <span class="token comment">/* just right? */</span> <span class="token punctuation">}</span></code></pre>
<p>Nicole Sullivan coined the term
<a href="http://www.stubbornella.org/content/2011/10/08/scope-donuts/">“donut” scope</a> for this issue in 2011 –
because the scope can have a hole in the middle.
It would be useful for authors
to express this DOM-fragment
“ownership” more clearly in native HTML/CSS.</p>
<h3 id="popular-tooling-for-modular-css">Popular tooling for modular CSS <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#popular-tooling-for-modular-css">¶</a></h3>
<p>CSS Modules, Vue, Styled-JSX, and other tools
often use a similar pattern
(with slight variations to syntax) –
where “scoped” selectors only apply to
the locally described DOM fragment,
and not descendants.</p>
<p>In Vue single file components,
authors can write html templates
with “scoped” style blocks:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- component.vue --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>sub-component</span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>sub-component</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span> <span class="token attr-name">scoped</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br /><span class="token selector">.component</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.sub-component</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span><br /><br /><span class="token comment"><!-- sub-component.vue --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sub-component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span> <span class="token attr-name">scoped</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br /><span class="token selector">.sub-component</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>While the language is similar to shadow-DOM in many ways,
the output is quite different –
and much less isolated.
The components remain part of the global scope,
and only the explicitly “scoped” styles are contained.
That’s often achieved by adding automated unique attributes
to each element based on the component(s) it belongs to:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>component<span class="token punctuation">"</span></span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element<span class="token punctuation">"</span></span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><br /><br /> <span class="token comment"><!-- nested component "shell" is in both scopes --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sub-component<span class="token punctuation">"</span></span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>component sub-component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element<span class="token punctuation">"</span></span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sub-component<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span></code></pre>
<p>And matching attributes are added to each selector:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* component.vue styles after scoping */</span><br /><span class="token selector">.component[scope=component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element[scope=component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.sub-component[scope=component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* sub-component.vue styles after scoping */</span><br /><span class="token comment">/* note that both style `.element` without any overlap or naming conflicts */</span><br /><span class="token selector">.sub-component[scope=sub-component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element[scope=sub-component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span></code></pre>
<ul>
<li>The donut is achieved by selectively adding attributes</li>
<li>Proximity-weight is achieved only through limiting the donut of scope,
so that outer values are less likely to “bleed” in</li>
<li>Added attribute gives scoped styles <em>some minimal</em>
extra specificity weight in the cascade
compared to non-scoped elements.</li>
</ul>
<h2 id="non-goals">Non-goals <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#non-goals">¶</a></h2>
<p>There is a more extreme isolation use-case.
It’s mostly used for “widgets” that will appear unchanged
across multiple projects –
but sometimes also in component libraries
on larger projects.</p>
<p>Full isolation blocks off a fragment of the DOM,
so that it <em>only</em> accepts styles that are
explicitly scoped.
General page styles do not apply.</p>
<p>We don’t think this is the most common concern for authors,
but it has received the most attention.
Shadow DOM is entirely constructed around this behavior.</p>
<p>We have not attempted to address that form of scope in this proposal –
it feels like a significantly different approach
that already has work underway.</p>
<p>See Yu Han’s proposals for
<a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#should-we-be-building-on-shadow-dom">building on shadow DOM</a>
below.</p>
<h2 id="proposed-solution">Proposed Solution <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#proposed-solution">¶</a></h2>
<p>In the long-standing
<a href="https://github.com/w3c/csswg-drafts/issues/3547">“Bring Back Scope”</a>
issue-thread,
Giuseppe Gurgone
<a href="https://github.com/w3c/csswg-drafts/issues/3547#issuecomment-524206816">suggests a syntax</a> building on the original un-implemented <code>@scope</code> spec,
but adding a lower boundary:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span><span class="token property">from</span><span class="token punctuation">:</span> .carousel<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">to</span><span class="token punctuation">:</span> .carousel-slide-content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>That seems like a good start,
but we’ve worked to simplify and clarify
where possible.</p>
<h3 id="defining-scopes-the-scope-rule">Defining scopes: the <code>@scope</code> rule <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#defining-scopes-the-scope-rule">¶</a></h3>
<p><em>The CSSWG has resolved to
put this work in
<a href="https://drafts.csswg.org/css-cascade-6/#scoped-styles">CSS Cascading and Inheritance Level 6</a>,
and rename <a href="https://drafts.csswg.org/css-scoping/">CSS Scoping</a>
to be about Shadow-DOM specifically.</em></p>
<p>The current syntax proposal for <code>@scope</code> is:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> [<span class="token punctuation">(</span><scope-start><span class="token punctuation">)</span>]? [to <span class="token punctuation">(</span><scope-end><span class="token punctuation">)</span>]?</span> <span class="token punctuation">{</span><br /> <stylesheet><br /><span class="token punctuation">}</span></code></pre>
<p>This approach is designed to keep scoping confined to CSS
(no need for a new HTML attribute),
flexible
(scopes can overlap as needed),
and non-invasive
(global styles continue to work as expected).
Existing tools would still be able to
provide syntax sugar for single-file components –
automatically generating the <code>@scope</code> rule –
but move the primary functionality into CSS.</p>
<p>The <code>@scope</code> prelude syntax is made up of two parts,
a ‘scope start’ and ‘scope end’ clause.</p>
<h4 id="defining-the-scope-root-scope-start">Defining the scope root: <code><scope-start></code> <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#defining-the-scope-root-scope-start">¶</a></h4>
<p>The <code>(<scope-start>)</code> clause,
defines the root of the scope
with a forgiving selector list:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media-block<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>That creates a ‘scoped’ tree fragment
that starts from any matched scope root element.
Both the matched element
and all its descendants
are <em>in the generated scope</em>.</p>
<p>The scope root selector is optional
when styles are nested in the DOM.
This allows a DOM-nested stylesheet
to implicitly scope styles,
using the direct parent ‘owner’ node of the <code><style></code> element
(or the containing tree
for constructable stylesheets with no owner node):</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br /> <span class="token atrule"><span class="token rule">@scope</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>this is red<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>not red<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>That would be equivalent to:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>foo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br /> <span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>#foo<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>this is red<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>not red<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>The only difference is in the resulting
selector specificity,
which we describe in more detail below.</p>
<h4 id="defining-scope-boundaries-scope-end">Defining scope boundaries: <code><scope-end></code> <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#defining-scope-boundaries-scope-end">¶</a></h4>
<p>The optional scope-end clause accept a list of selectors
that represent lower-boundary “slots” in the scope.
The <code><scope-end></code> clause is a forgiving selector list,
which is ‘scoped’ by the <code><scope-start></code> clause.
The targeted lower-boundary elements are excluded from the scope,
along with all their descendants:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media-block<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>The above example would only match <code>img</code> elements
that are inside <code>.media-block</code> –
<em>but not if there are intervening <code>.content</code> elements
between the scope root and selector target</em>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>media-block<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token comment"><!-- this img is in the .media-block scope --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token comment"><!-- this ends the scope --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token comment"><!-- this img is NOT in scope --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Authors can write ‘inclusive’ lower boundaries
by appending <code>> *</code> to scope-end selectors.
In the example above,
if the <code>.content</code> element should be included in the scope,
while excluding its descendants,
the rule would become:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media-block<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content > *<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.content</span> <span class="token punctuation">{</span> <span class="token comment">/* This is now in scope */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="nesting-scopes">Nesting scopes <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#nesting-scopes">¶</a></h3>
<p>When <code>@scope</code> rules are nested,
the inner <code><scope-start></code> selectors
are ‘scoped’ by the outer scope definition.
This can be used to narrow down a scope fragment.
However, further nested selectors
still only have a single <em>scope</em>
defined by a single <em>scope-root</em>
and lower <em>scope boundaries</em>.</p>
<p>Note: This is not the same as the <em>intersection</em> of scopes,
since the order is relevant.
Each nested scope is ‘scoped’ by the previous,
and can only match
if the new scope root
is ‘in’ the previous scope.</p>
<h3 id="selecting-a-scope-root-the-scope-pseudo-class">Selecting a scope root: the <code>:scope</code> pseudo-class <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#selecting-a-scope-root-the-scope-pseudo-class">¶</a></h3>
<p>(This section matches the proposed resolution
<a href="https://github.com/w3c/csswg-drafts/issues/8377">of issue #8377</a>,
rather than the current specification)</p>
<p>The existing
<a href="https://www.w3.org/TR/selectors-4/#the-scope-pseudo">Reference Element Pseudo-class</a>
(<code>:scope</code>)
can be used in a scoped selector
to reference the scoping element.</p>
<p>When not explicitly used in a scoped selector,
the <code>:scope</code> pseudo is an implied ancestor:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* :scope img */</span> <span class="token punctuation">}</span><br /> <span class="token selector">.content</span> <span class="token punctuation">{</span> <span class="token comment">/* :scope .content */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>However, authors can establish
an explicit or different relationship to the scope root
by including either <code>:scope</code> or <code>&</code> in the selector.
These <em>scope-containing</em> selectors
do not get the implicit ancestor prepended:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">:scope img</span> <span class="token punctuation">{</span> <span class="token comment">/* :scope img */</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope .content</span> <span class="token punctuation">{</span> <span class="token comment">/* :scope .content */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-mode .media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">:scope</span> <span class="token punctuation">{</span> <span class="token comment">/* :scope */</span> <span class="token punctuation">}</span><br /> <span class="token selector">:root :scope > img</span> <span class="token punctuation">{</span> <span class="token comment">/* :root :scope > img */</span> <span class="token punctuation">}</span><br /> <span class="token selector">.sidebar:scope</span> <span class="token punctuation">{</span> <span class="token comment">/* .sidebar:scope */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Note that <em>scope-containing</em> selectors
are able to reference the entire DOM tree.
Only the matched subject of the selector
is required to be <em>in scope</em>.</p>
<p>In many ways,
the <code>:scope</code> selector behaves
much like the <code>&</code> in CSS Nesting,
or the functional pseudo-class <code>:is(<scope-root>)</code>.
However, unlike the nesting <code>&</code>
or the <code>:is()</code> pseudo,
the <code>:scope</code> selector
refers specifically
to the root element of a scope -
so the following will not match
nested <code>.media</code> elements,
even if they are in-scope:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">:scope :scope</span> <span class="token punctuation">{</span> <span class="token comment">/* no match */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Instead, we could use any of the following:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* select nested .media inside the scope */</span><br /> <span class="token selector">.media</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope .media</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">& &</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope &</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="scope-in-the-cascade">Scope in the cascade <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#scope-in-the-cascade">¶</a></h3>
<p>The <code>@scope</code> rule has a double-impact
on the cascade of scoped selectors –
as part of specificity,
and then again in relation to proximity.</p>
<h4 id="scope-specificity">Scope Specificity <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#scope-specificity">¶</a></h4>
<p>While ‘scope’ and ‘specificity’
have some overlap –
and both impact on the priority
of a declaration –
we believe they should be handled separately
in the cascade.
Scope selectors are used for the purpose of scoping,
and should not have their specificity added
to the selectors that they scope.</p>
<p>A small amount of (class-level) specificity
can be added with explicit use of <code>:scope</code>,
and the full weight of the scoping selectors
can be added via the <code>&</code> selector:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>#media<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* [0, 0, 1] */</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope img</span> <span class="token punctuation">{</span> <span class="token comment">/* [0, 1, 1] */</span> <span class="token punctuation">}</span><br /> <span class="token selector">& img</span> <span class="token punctuation">{</span> <span class="token comment">/* [1, 0, 1] */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This provides flexibility for authors
without increasing specificity by default.</p>
<p>Some <a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#alternative-approaches-to-specificity">alternative approaches</a>
are discussed below.</p>
<h4 id="scope-proximity">Scope Proximity <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#scope-proximity">¶</a></h4>
<p>The scope syntax itself solves the issue of
naming-conflicts, with lower-boundaries and ownership.
But the issue of <em>proximity</em> requires a change in the Cascade.
For that, we’re introducing a new step
in the Cascade Sorting logic,
called <em>scope proximity</em>.</p>
<p>The <em>proximity</em> of a selector
is a single number
representing the ‘generational’ distance
between a matched selector subject
and the selector’s <em>scope root</em> in the DOM tree.</p>
<p>(Note that nested scopes still result in a single root)</p>
<p>A direct parent/child relationship has a
proximity of <code>1</code>, while further nested subjects
will have higher proximity values.
If the subject of a selector
is itself the scope root,
then the proximity is <code>0</code>.
If no scope root is defined,
the proximity value is infinite.</p>
<p>In the cascade <em>scope proximity</em> step of the cascade,
lower proximity selectors
have precedence
over higher proximity selectors:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-theme<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.dark-theme<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> plum<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>light-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>purple<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>While the more nested link
is matched by both selectors,
the selector with ‘lower proximity scope’
(the <code>.light-theme</code> root)
is applied.</p>
<p>Given the same proximity of multiple scopes,
source order would continue to be the final cascade filter:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-theme<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.special-links<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> maroon<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>special-links light-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>maroon<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>There has been some debate about
<a href="https://github.com/w3c/csswg-drafts/issues/6790">the priority of cascade proximity</a>
in relation to selector specificity.</p>
<ul>
<li>If specificity is given more weight in the cascade,
then proximity is only considered
for otherwise-equal-specificity selectors.
We’ve been calling this <em>weak proximity</em>.</li>
<li>If proximity has more weight in the cascade,
then specificity is only compared within equal scopes.
We’ve been calling this <em>strong proximity</em>.</li>
</ul>
<p>Since both specificity and proximity are heuristics,
it’s not clear which one is a ‘better’
approximation of author intent.
The more common preference
has been for a <em>weak proximity</em> –
since that leaves authors with more tools
for resolving conflicts in either direction,
through layering, or specificity adjustments.
This is also the behavior applied
by the existing browser prototype.</p>
<p>That means, currently,
<strong>specificity will override scope proximity</strong>.
Given the following CSS,
a paragraph matched by both rules
would be <code>red</code>,
thanks to the added specificity:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> aside</span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">aside#sidebar p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<p>This is a major departure from both
the <a href="https://www.w3.org/TR/css-scoping-1/">original scope specification</a>
and Shadow-DOM <a href="https://drafts.csswg.org/css-cascade-5/#cascade-context">encapsulation context</a>,
which override specificity.</p>
<p>We <a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#where-does-scope-fit-in-the-cascade">discuss this in more detail below</a>.</p>
<h2 id="key-scenarios">Key scenarios <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#key-scenarios">¶</a></h2>
<h3 id="avoid-naming-conflicts-without-custom-conventions">Avoid naming conflicts without custom conventions <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#avoid-naming-conflicts-without-custom-conventions">¶</a></h3>
<p>Authors currently rely on intricate naming conventions
to avoid duplicate naming within components:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.article__title</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">.article__meta</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.form__title</span> <span class="token punctuation">{</span><br /> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">.form__meta</span> <span class="token punctuation">{</span><br /> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Sometimes authors will automate that process,
and group names visually,
using nested syntax in a pre-processor like Sass:</p>
<pre class="language-scss"><code class="language-scss"><span class="token selector">.article </span><span class="token punctuation">{</span><br /> <span class="token selector"><span class="token parent important">&</span>__title </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector"><span class="token parent important">&</span>__meta </span><span class="token punctuation">{</span> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.form </span><span class="token punctuation">{</span><br /> <span class="token selector"><span class="token parent important">&</span>__title </span><span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector"><span class="token parent important">&</span>__meta </span><span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This syntax would provide a uniform solution
that is native to CSS.
Authors can reduce naming conflicts
across CSS “components”/“objects”
by scoping internal selectors
so they only match within a particular block:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>article<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.title</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">.meta</span> <span class="token punctuation">{</span> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>form<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.title</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">.meta</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="express-ownership-boundaries-in-nested-components">Express ownership boundaries in nested components <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#express-ownership-boundaries-in-nested-components">¶</a></h3>
<p>By adding lower boundaries or “slots” to the scope,
ownership becomes more clear
when the scopes are nested.</p>
<p>Using the example above,
we can allow comment forms to be nested inside articles
while continuing to maintain the
distinction between article-elements
and form-elements:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>article<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.comments<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.title</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">.meta</span> <span class="token punctuation">{</span> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.comments<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.title</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token selector">.meta</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="recursive-nesting-with-ownership">Recursive nesting with ownership <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#recursive-nesting-with-ownership">¶</a></h3>
<p>This can also be useful
when applying modifiers to components
that might nest indefinitely –
such as the popular “media-object”
containing a fixed image and responsive content
that flows around it.
Modifiers can be added to an outer component
without impacting nested components of the same type.</p>
<p>For example,
nested “media objects”
of different types:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>media reverse<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>media<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Rather than adding the <code>.reverse</code> modifier
to every element in the outer media block,
we can scope the effects of the modifier.
This could be done in a variety of ways,
but may be a good use-case for nesting scopes:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* only the inner image */</span><br /> <span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span><span class="token punctuation">:</span><span class="token property">scope</span><span class="token punctuation">:</span><span class="token keyword">not</span><span class="token punctuation">(</span>.reverse<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/* only the outer image */</span><br /> <span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span><span class="token punctuation">:</span>scope.reverse<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h3 id="recognizing-proximity-of-nested-components-without-lower-bounds">Recognizing proximity of nested components without lower-bounds <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#recognizing-proximity-of-nested-components-without-lower-bounds">¶</a></h3>
<p>As <a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#scope-proximity-in-the-cascade">demonstrated above</a>,
authors could establish
scope precedence
even when lower bounds are not required.
For example,
light and dark themes
that can be nested
in any arrangement:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.light-theme<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.dark-theme<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> plum<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>light-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>purple<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum again<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<h3 id="js-tools--single-file-components">JS tools & “single file components” <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#js-tools--single-file-components">¶</a></h3>
<p>Existing tools could move to automating
this syntax over time,
rather than using custom attributes,
since the result is very similar.
Without any changes visible to the user,
output that currently looks like:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* component.vue styles after scoping */</span><br /><span class="token selector">.component[scope=component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element[scope=component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">/* sub-component.vue styles after scoping */</span><br /><span class="token comment">/* note that both style `.element` without any overlap or naming conflicts */</span><br /><span class="token selector">.sub-component[scope=sub-component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token selector">.element[scope=sub-component]</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span></code></pre>
<p>Could be converted to:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* component.vue styles after scoping */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.component<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.sub-component<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">:scope</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /> <span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* sub-component.vue styles after scoping */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.sub-component<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">:scope</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /> <span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h2 id="detailed-design-discussion--alternatives">Detailed design discussion & alternatives <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#detailed-design-discussion--alternatives">¶</a></h2>
<h3 id="should-we-be-building-on-shadow-dom">Should we be building on Shadow DOM? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#should-we-be-building-on-shadow-dom">¶</a></h3>
<p>This is often the first question asked
of any scope-related proposal,
for good reason.
Shadow-DOM was designed for the express purpose
of encapsulating styles & behavior.</p>
<p>But “scope” can describe
a number of sometimes-contradictory use-cases,
which can’t all be solved using the same primitives.
Shadow-DOM starts from a few assumptions
that make sense in very specific use-cases,
but do not describe the majority of web design:</p>
<ul>
<li>Scoped styles match 1:1 with scoped elements in the DOM</li>
<li>Scopes nest, but they do not overlap</li>
<li>By default, no selectors should cross the encapsulation boundary</li>
</ul>
<p>Yu Han has
an <a href="https://docs.google.com/document/d/1hhjmuQE6BTTnAyKP3spDr8sU6lpXArh8LDfihZh78hw/edit?usp=sharinghttps://docs.google.com/document/d/1hhjmuQE6BTTnAyKP3spDr8sU6lpXArh8LDfihZh78hw/edit?usp=sharing">interesting proposal</a>
in two parts,
designed to improve shadow-DOM,
by providing some more flexibility:</p>
<ol>
<li>Allow shadow-DOM elements to opt-in to global styles</li>
<li>Allow light-DOM elements to opt-in to style isolation</li>
</ol>
<p>Those proposals
(along with declarative shadow-DOM)
would be very helpful for making strong isolation
more flexible for authors.
But even with that added flexibility,
many of the assumptions & limitations remain.</p>
<p>Shadow-DOM is not designed
for the common “design system” approach,
where patterns overlap in more fluid ways,
and global styles are expected to “flow through”
and tie all the pieces together.</p>
<h3 id="are-scope-attributes-useful-in-html">Are scope attributes useful in html? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#are-scope-attributes-useful-in-html">¶</a></h3>
<p>Many scope proposals include
a scope attribute in HTML.
That’s required for the full-isolation use-case –
where elements need to opt-in or out of global page styles up-front.
But following that path would make scope
much more similar to shadow-DOM
in it’s limitations:</p>
<ul>
<li>Either we need a way to define lower-boundaries in the markup as well,
or we make scopes exclusive/non-overlapping</li>
<li>Authors have to manage scope in both HTML and CSS</li>
</ul>
<p>Some authors might appreciate the
“automatic” nature of that approach,
but they could achieve the same goals
with a naming convention or tooling:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>[data-scope=main]<span class="token punctuation">)</span> to <span class="token punctuation">(</span>[data-scope]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>[data-scope=note]<span class="token punctuation">)</span> to <span class="token punctuation">(</span>[data-scope]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>This text is blue<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">data-scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>main<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>This text is green<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">data-scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>note<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>This text is gray<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">data-scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>note<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>This text is gray<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<h3 id="do-we-need-special-handling-around-the-shadow-dom">Do we need special handling around the shadow-DOM? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#do-we-need-special-handling-around-the-shadow-dom">¶</a></h3>
<p>This proposal would have no impact
on existing shadow DOM behavior.
Scoped styles can be used in both light and shadow DOM.
Both scopes & scoped styles respect the shadow boundary,
like any other CSS rules.</p>
<p>There are still some
<a href="https://github.com/w3ctag/design-reviews/issues/593#issuecomment-768992509">questions raised by Rune Lillesveen</a>
about what selectors
would be allowed inside
a shadow-DOM encapsulated scope rule:</p>
<blockquote>
<p>For selectors in @scope rules in shadow trees,
we should figure out which restrictions apply wrt
matching elements outside the shadow tree.
<code>@scope</code> rules in shadow trees should not be able
to target elements outside the shadow tree,
but what about <code>:host</code>/<code>:host-context</code>?</p>
<ul>
<li><code>@scope (:host(.a))</code> : should not match</li>
<li><code>@scope (:host(.a) .b)</code> : should match?</li>
<li><code>@scope (::slotted(.a))</code> : should not match</li>
<li><code>@scope (::part(my-part))</code> : should not match</li>
</ul>
</blockquote>
<h3 id="a-js-api-for-fetching-donut-scope-elements">A JS API for fetching “donut scope” elements? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#a-js-api-for-fetching-donut-scope-elements">¶</a></h3>
<p>Given a tree fragment,
JS is able to match elements <em>within</em> a donut scope:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> matches <span class="token operator">=</span> root<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><br /> <span class="token string">"*:not(:scope boundary, :scope boundary *)"</span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>With CSS nesting,
it would also be possible
to use the <code>@scope</code> rule directly:</p>
<pre class="language-js"><code class="language-js">document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"@scope (root) to (boundary) { * }"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>But those solutions
do not provide any way to
select and capture the “donuts” themselves,
only select elements inside the donut.</p>
<p>Rather than adding a new selector format,
the CSSWG has defined
a second “exclusions” parameter
on methods like <code>querySelector()</code> and <code>querySelectorAll()</code>:</p>
<pre class="language-js"><code class="language-js">document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"root"</span><span class="token punctuation">,</span> <span class="token string">"boundary"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This has been agreed on by the CSSWG.</p>
<h3 id="how-does-scope-interact-with-the-nesting-module">How does scope interact with the nesting module? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#how-does-scope-interact-with-the-nesting-module">¶</a></h3>
<p>Conceptually,
there is a fair amount of overlap
between CSS ‘scoping’
and some use-cases for
<a href="https://drafts.csswg.org/css-nesting/">CSS Nesting</a>.</p>
<p>Scope can be used for a subset
of nesting use-cases,
where the nested-selector subject is a descendant
of the parent-selector subject.
In those cases,
scope provides additional benefits,
such as lower boundaries and proximity weighting.</p>
<p>Ideally,
we should allow authors to combine these two approaches,
or move smoothly between them.</p>
<p>In the nesting syntax, for example,
we would allow
the <code>@scope</code> rule to be nested inside
an existing selector block.
The scope rule
could establish a scope-root based on the parent selector,
using <code>&</code>.
These examples
would have the same meaning:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* explicit scope with root */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.media</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* nested scope with explicit nesting root */</span><br /> <span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>&<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Similarly,
we can allow <code>&</code> in a scoping context
to refer to the scope root selector:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* explicit scope with root */</span><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.media<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">& > img</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is much like using <code>:scope</code>,
but they have slightly different matching behavior.
The <code>&</code> selector
represents an entire selector list,
and could match nested <code>.media</code> elements –
While the <code>:scope</code> selector
represents a specific scoping root element in the DOM.</p>
<h3 id="what-happens-when-the-root-element-matches-the-lower-boundary">What happens when the root element matches the lower-boundary? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#what-happens-when-the-root-element-matches-the-lower-boundary">¶</a></h3>
<p>It’s possible that authors
might use the same selector for both
the root and lower-boundary of a scope
(or use different selectors that match the same element):</p>
<pre class="language-css"><code class="language-css"><span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> rebeccapurple<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.dark-theme<span class="token punctuation">)</span> to <span class="token punctuation">(</span>[class*=<span class="token string">'-theme'</span>]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> plum<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Since the lower boundary selector is scoped
by the root selector,
it is implicitly a descendant
unless otherwise specified:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- dark-theme scope root --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /><br /> <span class="token comment"><!-- dark-theme scope boundary --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>light-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>rebeccapurple<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><br /> <span class="token comment"><!-- dark-theme scope boundary & nested scope root --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dark-theme<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>plum<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>There aren’t any obvious use-case
for single-element scopes,
but they can be achieved with the
explicit <code>:scope</code> selector:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>.self<span class="token punctuation">)</span> to <span class="token punctuation">(</span><span class="token punctuation">:</span>scope *<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span></code></pre>
<h3 id="where-does-scope-fit-in-the-cascade">Where does scope fit in the cascade? <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#where-does-scope-fit-in-the-cascade">¶</a></h3>
<p>For a more detailed exploration of this,
see: <a href="https://css.oddbird.net/scope/cascade/">scope in the cascade</a>.</p>
<h4 id="the-2014-scope-proposal">The 2014 scope proposal <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the-2014-scope-proposal">¶</a></h4>
<p>The <a href="https://www.w3.org/TR/css-scoping-1/">original scope specification</a>
put scope above specificity in the cascade,
and the layering was importance-relative:</p>
<blockquote>
<p>For normal declarations the inner scope’s declarations override,
but for ‘’!important’’ rules outer scope’s override.</p>
</blockquote>
<p>That would mean first
that scope takes precedence over specificity.
By default, the more locally-scoped style always wins:</p>
<p>In this example
from the outdated specification,
a paragraph matched by both selectors
would be green:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> aside</span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">aside#sidebar p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<p>But the roles would reverse
when <code>!important</code> is used,
and the following example paragraph would be red:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> aside</span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">aside#sidebar p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<h4 id="shadow-dom-encapsulation-context">Shadow-DOM encapsulation context <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#shadow-dom-encapsulation-context">¶</a></h4>
<p>Shadow-DOM <a href="https://drafts.csswg.org/css-cascade-5/#cascade-context">encapsulation context</a>
also comes above/before specificity in the cascade,
with an importance-reversal.
To quote the spec:</p>
<blockquote>
<p>When comparing two declarations
that are sourced from different encapsulation contexts,
then for normal rules the declaration from the outer context wins,
and for important rules the declaration from the inner context wins.</p>
</blockquote>
<p>This is the opposite of the original scope proposal,
and means:</p>
<blockquote>
<p>…normal declarations belonging to an encapsulation context
can set defaults that are easily overridden by the outer context,
while important declarations belonging to an encapsulation context
can enforce requirements that cannot be overridden by the outer context.</p>
</blockquote>
<h4 id="the-case-for-less-isolation-and-weak-proximity">The case for less isolation, and weak proximity <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the-case-for-less-isolation-and-weak-proximity">¶</a></h4>
<p>We have intentionally gone in the other direction,
making scope proximity <em>less powerful</em> than specificity
in the cascade.
There is clearly interest in both strong & weak approaches to scope,
but we believe encapsulation context
can be expanded and improved on
for the high-isolation use-cases.
Meanwhile low-isolation scope has not been addressed.</p>
<p>By placing <em>scope proximity</em>
below/after specificity in the cascade,
We are explicitly & intentionally allowing
more global styles to flow through,
interact with,
and even override scoped styles:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">@scope (aside)</span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">aside#sidebar p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>aside</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sidebar<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>This is red<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>aside</span><span class="token punctuation">></span></span></code></pre>
<p>The primary use-case that we’re trying to address
is one in which component-styles are “locked-in”
to avoid cross-contamination,
but global styles are used to “tie it all together”
with consistent patterns like typography and branding.
The desired behavior is to prevent scoped styles from leaking out,
without getting in the way of global patterns
that should flow through easily.</p>
<p>If we give scope proximity more weight than specificity,
authors are left with very few tools to manage that relationship.
By putting proximity <em>below</em> specificity,
authors can manage it in several ways:</p>
<ul>
<li>Adjust specificity to reflect desired priority,
with equal specificity to trigger <em>proximity</em> results</li>
<li>Add lower boundaries to avoid overlap of styles</li>
</ul>
<p>This <em>in-but-not-out</em> approach
also matches the existing JS tools & CSS naming conventions
that authors already use.
Those tools add lower-boundaries,
and a single attribute-selector of increased specificity –
very easy to override from the global scope.
We think this low-weight approach to scope is also backed up by…</p>
<ul>
<li>much of the <a href="https://github.com/w3c/csswg-drafts/issues/3547">long-running conversation on CSSWG</a></li>
<li>a <a href="https://twitter.com/TerribleMia/status/1351247559738621952">quick informal survey on Twitter</a></li>
<li>Nicole Sullivan’s <a href="https://docs.google.com/presentation/d/1Ki-IUCEWU-mNlS-019QVV9I9JsytvafQJHTxpBNfYvI/edit?usp=sharing">explainer from a couple years ago</a></li>
<li>The majority of <a href="https://github.com/w3c/csswg-drafts/issues/6790">responses to the open issue</a></li>
</ul>
<p>Anecdotally,
many CSS beginners surprised
that the fallback for specificity
is source-order rather than proximity.
This proposal would allow authors to opt-into
that expected proximity-over-source-order fallback behavior.</p>
<p>Meanwhile,
encapsulation could be expanded
for <a href="https://docs.google.com/document/d/1hhjmuQE6BTTnAyKP3spDr8sU6lpXArh8LDfihZh78hw/edit?usp=sharinghttps://docs.google.com/document/d/1hhjmuQE6BTTnAyKP3spDr8sU6lpXArh8LDfihZh78hw/edit?usp=sharing">use in the light DOM</a>,
and proposal would continue to be distinct –
covering a significantly different set of use-cases.</p>
<h4 id="alternative-approaches-to-specificity">Alternative approaches to specificity <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#alternative-approaches-to-specificity">¶</a></h4>
<p>Existing tools
achieve donut scope
by appending a single attribute
to each selector.
If we wanted to match that behavior,
we could give <code>:scope</code>
the normal pseudo-class weight –
even when implicitly added.
Given the following code:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>#media<span class="token punctuation">)</span> to <span class="token punctuation">(</span>.content<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* implied :scope ancestor */</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope img</span> <span class="token punctuation">{</span> <span class="token comment">/* explicit :scope ancestor */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This approach would result in
a specificity of <code>[0, 1, 1]</code>
for each selector.</p>
<p><a href="https://twitter.com/sarasoueidan/status/1351248295969103873?s=21">Sara Soueidan has also proposed</a>
giving <code>@scope</code> the selector-weight of an <code>#ID</code>.
That would acknowledge the targeting weight of scopes,
without making them override all specificity.
While it’s an interesting idea,
it seems less-intuitive & less flexible
than the alternatives.</p>
<p>The original proposal
was designed to match CSS Nesting
when it comes to specificity.
We can do that by
applying the scope-root specificity
to the overall specificity of each selector –
as though de-sugared to <code>:is(<scope-start>)</code>.
The following examples
would all have a specificity of <code>[1, 0, 1]</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@scope</span> <span class="token punctuation">(</span>#footer<span class="token punctuation">,</span> footer<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token comment">/* :is(#footer, footer) a */</span> <span class="token punctuation">}</span><br /> <span class="token selector">:scope a</span> <span class="token punctuation">{</span> <span class="token comment">/* :is(#footer, footer) a */</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This matches the behavior of <code>&</code>
in nested selectors,
as well as ensuring the same specificity
with or without the explicit use of <code>:scope</code>.</p>
<p>However,
the group has resolved in favor
of scope not having any impact on specificity.
While specificity is often
the way authors think about managing priority,
scope is a different concern
(like layers or shadow context)
and there is no reason to muddy the water.</p>
<h2 id="spec-history--context">Spec History & Context <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#spec-history--context">¶</a></h2>
<p>Besides the tooling that has developed,
there are several current & former specs
that are relevant here…</p>
<h3 id="css-scoping">CSS Scoping <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#css-scoping">¶</a></h3>
<ul>
<li><a href="https://www.w3.org/TR/css-scoping-1/">First Public Working Draft</a></li>
<li><a href="https://drafts.csswg.org/css-scoping/">Editors Draft</a></li>
</ul>
<p>There is often pushback to the question of scope,
since the initial specification was never implemented,
and Shadow DOM was seen as a path forward.
While the current editors draft
is primarily concerned with Custom Elements & Shadow DOM,
this spec initially contained a full set of scoping features
that have since been removed:</p>
<p>A <code><style scoped></code> attribute,
which would apply styles
scoped to a particular DOM sub-tree.
This had a few limitations:</p>
<ul>
<li>Authors need to repeat styles in the DOM for every instance of the scope</li>
<li>Those style need to live in distinct stylesheets</li>
</ul>
<p>The use-cases that necessitate that approach
are now being handled by shadow encapsulation,
which frees us up to consider different use-cases now.</p>
<p>The spec also included <code>@scope</code> blocks in CSS,
which would help alleviate both issues.
Scoping has two primary effects:</p>
<ol>
<li>The selector of the scoped style rule
is restricted to match only elements within a subtree of the DOM</li>
<li>The cascade prioritizes scoped rules over un-scoped ones,
regardless of specificity</li>
<li>Important declarations would flip the cascade order of scopes</li>
</ol>
<p>Point 1 is limited by the need for lower scope boundaries,
or “donut scope”.</p>
<p>Points 2 & 3 give scope <em>significant</em> power in the cascade –
power that we now plan to provide through Cascade Layers.
While there are instances where the semantics of
layering, scoping, and containment
might reasonably overlap –
the combinations remain more flexible
when each one has it’s own syntax.</p>
<p>In this proposal,
scope is only given a <em>minimal</em> role in the cascade,
and mostly acts as a protection from naming conflicts.</p>
<h3 id="css-selectors---level-4"><a href="https://www.w3.org/TR/selectors-4/">CSS Selectors - Level 4</a> <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#css-selectors---level-4">¶</a></h3>
<ul>
<li><a href="https://www.w3.org/TR/selectors-4/#scoping">Scoped Selectors</a>,
which only refer to a subtree or fragment of the document</li>
<li><a href="https://www.w3.org/TR/selectors-4/#the-scope-pseudo">Reference Element</a>
(<code>:scope</code>) pseudo-class “:scope elements” or the root of any scope
(currently used in JS APIs only)</li>
</ul>
<h3 id="css-cascade---level-4"><a href="https://www.w3.org/TR/css-cascade-4/">CSS Cascade - Level 4</a> <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#css-cascade---level-4">¶</a></h3>
<ul>
<li><a href="https://www.w3.org/TR/css-cascade-4/#change-2018-drop-scoped">Removes</a>
“scoping” from the cascade sort criteria,
because it has not been implemented.</li>
<li>Adds <a href="https://www.w3.org/TR/css-cascade-4/#cascade-context">encapsulation context</a>
to the cascade, for handling Shadow DOM
<ul>
<li>Outer context wins for <em>normal</em> layer conflicts</li>
<li>Inner context wins for <code>!important</code> layer conflicts</li>
</ul>
</li>
</ul>
<h2 id="stakeholder-feedback--opposition">Stakeholder Feedback / Opposition <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#stakeholder-feedback--opposition">¶</a></h2>
<ul>
<li>Chromium : Positive –
Google was involved in developing this proposal</li>
<li>Gecko : No signals</li>
<li>Webkit : No signals</li>
</ul>
<h2 id="references--acknowledgements">References & acknowledgements <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#references--acknowledgements">¶</a></h2>
<p>Related/previous issues and discussions:</p>
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/3547">Bring Back Scope</a>:
<ul>
<li><a href="https://github.com/w3c/csswg-drafts/issues/3547#issuecomment-524206816">@scope with lower-bounds</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/3547#issuecomment-693022720">@scope with name & attribute</a></li>
</ul>
</li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/5057">Selector Boundaries</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/270">CSS Namespaces</a>
(<a href="https://github.com/w3c/csswg-drafts/issues/270#issuecomment-231586786">priorities</a>)</li>
</ul>
<p>In addition to the open issue threads mentioned above,
thanks for valuable feedback and advice from:</p>
<ul>
<li>Anders Hartvoll Ruud</li>
<li>Giuseppe Gurgone</li>
<li>Ian Kilpatrick</li>
<li>Keith Grant</li>
<li>Kenneth Rohde Christiansen</li>
<li>Lea Verou</li>
<li>Mason Freed</li>
<li>Nicole Sullivan</li>
<li>Rune Lillesveen</li>
<li>Sara Soueidan</li>
<li>Tab Atkins</li>
<li>Theresa O’Connor</li>
<li>Una Kravets</li>
<li>Yu Han</li>
</ul>
<h2 id="change-log">Change log <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#change-log">¶</a></h2>
<h3 id="20230413">2023.04.13 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20230413">¶</a></h3>
<ul>
<li>Update the sections on specificity.</li>
</ul>
<h3 id="20230321">2023.03.21 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20230321">¶</a></h3>
<ul>
<li>Updated to match resolutions from the previous year,
and the updated Working Draft</li>
</ul>
<h3 id="20210824">2021.08.24 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210824">¶</a></h3>
<ul>
<li>LINK to <a href="https://css.oddbird.net/scope/syntax/">syntax comparison</a></li>
</ul>
<h3 id="20210203">2021.02.03 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210203">¶</a></h3>
<ul>
<li>CLARIFY approaches to specificity</li>
<li>CLARIFY behavior when scope boundaries match scope root
(e.g. <code>@scope (a) to (a)</code>)</li>
<li>CLARIFY differences between scope and shadow-DOM</li>
<li>CLARIFY shadow-DOM issues to be addressed</li>
<li>CLARIFY redundant discussion sections</li>
<li>CLARIFY donut scope in JS</li>
<li>Discussion of tree-abiding pseudo elements as scope roots</li>
</ul>
<h3 id="20210129">2021.01.29 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210129">¶</a></h3>
<ul>
<li>CLARIFY reasons for using <code>:scope</code> rather than <code>&</code></li>
</ul>
<h3 id="20210128">2021.01.28 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210128">¶</a></h3>
<ul>
<li>NEW: Add proposal for <code>:in()</code> donut-scope selector</li>
<li>CHANGE: Update specificity definition to reflect pseudo-class alternative</li>
<li>CHANGE: Remove <code>&</code> syntax, to avoid conflicts with nesting</li>
<li>Detailed discussion of selector syntax</li>
<li>Detailed discussion of JS API for fetching a donut scope</li>
<li>Detailed discussion of scope-nesting</li>
</ul>
<h3 id="20210127">2021.01.27 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210127">¶</a></h3>
<ul>
<li>CHANGE: Follow the <a href="https://drafts.csswg.org/css-nesting/">nesting module</a> rules
for combining specificity of the scope selector
with specificity of nested/scoped selectors.</li>
<li>CHANGE: Allow <a href="https://drafts.csswg.org/css-nesting/">nesting module</a> syntax
to be used <a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#the--nesting-selector">in scoped selectors</a></li>
<li>CLARIFY: The scope-root is prepended (as an ancestor)
to all scoped selectors
unless explicitly placed by use of <code>&</code> or <code>:scope</code></li>
<li>CLARIFY: The placement of
<a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#where-does-scope-fit-in-the-cascade">scope in the cascade</a>,
and reasons for allowing specificity
to flow-through scopes in a single direction</li>
</ul>
<h3 id="20210118">2021.01.18 <a class="header-anchor" href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#20210118">¶</a></h3>
<ul>
<li>Update acknowledgements</li>
<li>CHANGE: Require <code>:scope</code> pseudo-class in scoped selectors that reference
<a href="https://css.oddbird.net/changelog/scope/explainer/2022-10-03t000000000z/#can-scoped-selectors-reference-external-context">context outside of the scope</a></li>
<li>CLARIFY: Shadow DOM behavior (scope respects shadow boundaries)</li>
<li>CLARIFY: question about selector-lists in scope-root syntax</li>
<li>CLARIFY: consistently use parenthesis around scope-root selectors</li>
<li>CLARIFY: additional discussion of scope in the cascade</li>
<li>CLARIFY: fix typo in proximity example</li>
</ul>