Skip to content

feat: container queries, style() and scroll-state() - #59

Open
Kristofer Baxter (kristofer-baxter) wants to merge 1 commit into
microsoft:mainfrom
kristofer-baxter:oss/container-queries
Open

feat: container queries, style() and scroll-state()#59
Kristofer Baxter (kristofer-baxter) wants to merge 1 commit into
microsoft:mainfrom
kristofer-baxter:oss/container-queries

Conversation

@kristofer-baxter

@kristofer-baxter Kristofer Baxter (kristofer-baxter) commented Sep 2, 2026

Copy link
Copy Markdown

1 of 6. This one applies to main on its own and is ready for review.

It is the first of six commits that split #57. Each adds one kind of scope, and each stands on its own: no error recovery, and no change to how the grammar reads anything it already read. The five behind it are open as drafts, because a pull request from a fork can only be based on main, and their diffs would otherwise include this one's changes.

Adds scopes for @container: the container name, size features, logical operators, style() queries and scroll-state() queries, including grouped and boolean forms. The body carries meta.at-rule.container.body.css, and both of its braces are scoped, matching the other block at-rules.

A query in parentheses may itself be a parenthesised query, so groups nest instead of closing at the first bracket, and <general-enclosed> is parsed so a bracket inside a string is not read as one. A <style-query> is parsed recursively, so not, grouped queries and ranges are not read as a declaration list. A feature value is a component value, so a function may stand in for a keyword.

style() holds a <declaration-value>, where a ; nested in brackets or a balanced block is part of the value. Its declaration-value region therefore ends only at } or ), so style(--x: [a;b]) keeps the whole value.

The container name is a <custom-ident>, so it may begin with an escape. Only none, and, not and or are reserved, so style and scroll-state are names in their own right, and only a bracket straight after either one makes it a query function. The query is optional, so a name may run into the body, and the prelude is a comma-separated list, so a name may start at a comma. The cost of a prelude is linear in its length: doubling a 12.5 KB prelude twice roughly doubles the time at each step, and 50 KB takes under half a second, which is well past any real stylesheet.

An unknown query function is <general-enclosed> wherever a query is valid, not only inside parentheses. A ) separates the identifier that follows it, so )and( needs no whitespace. A style range may be written without spaces, value first, or chained, and its custom property name stops at the comparison.

A size container feature takes a length or a ratio, and only orientation takes a keyword, so the values @media accepts are not query values here: @container (width: fullscreen) reads fullscreen as no more than an identifier.

A malformed prelude leaks its header scope to the rest of the file, which is what @media already does. @container has no semicolon form, so @container nonsense; leaks along with the rest.

Adds scopes for `@container`: the container name, size features, logical
operators, `style()` queries and `scroll-state()` queries, including
grouped and boolean forms. The body carries
`meta.at-rule.container.body.css` and both of its braces are scoped,
matching the other block at-rules.

A query in parentheses may itself be a parenthesised query, so the
groups nest rather than close at the first bracket, and
`<general-enclosed>` is parsed so a bracket inside a string is not read
as one. A `<style-query>` is parsed recursively, so `not`, grouped
queries and ranges are not read as a declaration list. A feature value
is a component value, so a function may stand in for a keyword.

`style()` holds a `<declaration-value>`, where a `;` nested in brackets
or a balanced block is part of the value. Its declaration-value region
therefore ends only at `}` or `)`, so `style(--x: [a;b])` keeps the
whole value.

The container name is a `<custom-ident>`, so it may begin with an
escape. Only `none`, `and`, `not` and `or` are reserved, so `style` and
`scroll-state` are names in their own right and only a bracket straight
after either one makes it a query function. The query is optional, so a
name may run into the body, and the prelude is a comma-separated list,
so a name may start at a comma. The
cost of a prelude is linear in its length: doubling a 12.5 KB prelude
twice roughly doubles the time each step, and 50 KB takes under half a
second, which is far past any real stylesheet.

An unknown query function is `<general-enclosed>` wherever a query is
valid, not only inside parentheses. A `)` separates the identifier that
follows it, so `)and(` needs no whitespace. A style range may be written
without spaces, value first, or chained, and its custom property name
stops at the comparison.

A size container feature takes a length or a ratio, and only
`orientation` takes a keyword, so the values `@media` accepts are not
query values here: `@container (width: fullscreen)` reads `fullscreen`
as no more than an identifier.

A malformed prelude leaks its header scope to the rest of the file,
which is what `@media` already does. `@container` has no semicolon
form, so `@container nonsense;` leaks with the rest of them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants