[CSSWG] Minutes CSS Values Breakout 2025-09-24 [css-values]

=========================================
  These are the official CSSWG minutes.
  Unless you're correcting the minutes,
 please respond by starting a new thread
   with an appropriate subject line.
=========================================


CSS Values 5
------------

  - RESOLVED: Make type(<url>) invalid in the attr() function (for now)
              (Issue #5079: attr()'s url type seems wrong)
  - RESOLVED: Simplify as much as possible (combining items when it
              can) for computed value (Issue #12290: Computed (vs used)
              value of mix() functions)
  - RESOLVED: Work on this in the next level (in consideration of all
              the complications outlined above) (Issue #11941: Allow
              `if()` in descriptors)
  - RESOLVED: type() values are parsed just as in custom properties.
              Other attr() type values (units % etc) are literals. Add
              a 'number' keyword for literal numbers (Issue #12479:
              Clarify how `<attr-unit>` should be parsed)
  - RESOLVED: Add color-scheme() test to both @container queries and
              if(). Open issue on what color-scheme() returns when used
              in color-scheme property (Issue #10577: Using `if()` to
              do dark/light switching of image urls)
  - RESOLVED: Explore options for making a syntactic distinction
              between input and interpolation options (Issue #12348:
              *-interpolate() grammar can lead to confusing values)

===== FULL MEETING MINUTES ======

Agenda: https://lists.w3.org/Archives/Public/www-style/2025Sep/0015.html

Present:
  Tab Atkins-Bittner
  Kevin Babbitt
  Oriol Brufau
  Emilio Cobos Álvarez
  Yehonatan Daniv
  Elika Etemad
  Roman Komarov
  Alan Stearns
  Miriam Suzanne
  Anne van Kesteren
  Lea Verou

Scribe: fantasai
Scribe's scribe: emilio, TabAtkins

CSS Values Level 5
==================

attr()'s url type seems wrong
-----------------------------
  github: https://github.com/w3c/csswg-drafts/issues/5079

  annevk: attr() started supporting types allowing to parse attributes
          according to specified CSS types
  annevk: Some issues here.
  annevk: One was about passing the base URL along. Some discussion
          about that, forget the outcome.
  astearns: Munira suggested making the type invalid
  annevk: That is my preferred solution as well

  annevk: Resolving as document stylesheet maybe works. But if you
          resolve and serialize, it ends up as all ASCII, which is all
          Western-centric. Not what you want to print.
  annevk: like domain name would become pyunicode
  annevk: If you want it as a formatting feature, you'd need to add a
          converter function
  annevk: which we haven't exposed previously, and has security
          implications
  annevk: Browsers have not yet converged on a single algorithm for
          that operation

  emilio: Multiple base URL is not an issue.
  emilio: That said, I don't disagree with making it invalid.
  annevk: So you'd parse it for each document separately?
  emilio: attr() is substituted at computed value time, so you need to
          have the element and the document
  annevk: So when you compute the value, you parse the attribute value
  emilio: Yes
  emilio: You can optimize some cases but basically yes
  annevk: So you go into CSSOM, you get attr()
  emilio: The stylesheet has a base URL of its own, but it's not the
          base URI we're talking about here.
  annevk: Does the CSSOM have an object representing attr() you can
          query?
  emilio: Maybe with typed OM
  emilio: But you don't know which element you're applying the
          function to
  emilio: It's likely that idk about typed OM internals much
  emilio: but I imagine it presents as a function value with a keyword
          inside
  emilio: Might be a string or whatever
  emilio: But it's tangential. You can share a stylesheet across
          multiple docs without an issue

  astearns: Problem is that making invalid breaks print formatters
  annevk: Yeah, some want to do things like print URL of the link. But
          then you run into the i18n problem. Only works well for ASCII
          inputs, and that doesn't feel responsible to standardize

  fantasai: was it the case that the existing formatters use a
            different syntax? In which case making type(<url>) invalid
            wouldn't change anything?
  fantasai: presumably they are stuck on whatever syntax they support
  fantasai: seems we could try to standardize what they support but
            dropping type() from the spec shouldn't change it
  fantasai: but agreed we shouldn't break their existing content
  fantasai: that'd be bad
  annevk: Unclear if they support the current syntax

  astearns: Could take a resolution that `type(<url>)` is invalid,
            hoping print formatters are not using that syntax yet?
  TabAtkins: I'm happy with that.
  TabAtkins: We can also put an allowance for e.g. non-security-
             sensitive uses such as printing
  annevk: Not so much security, but i18n concern
  annevk: If the idea of this function is to display the URL, then we
          should have a good way of displaying
  annevk: If we just echo the value, then it will work for Western docs
          but fall apart everywhere else
  annevk: In which case I'd rather avoid
  annevk: making something that works for a subset of the world
  astearns: By making invalid now, not blocking a future where we
            figure out how to make it work well for i18n
  annevk: Yes. We should probably get there eventually. Just not a
          priority to figure out for anyone atm
  astearns: Ok, let's take a resolution and see how Mike responds to it.
  PROPOSED: Make type(<url>) invalid in the attr() function (for now).
  <TabAtkins> +1

  RESOLVED: Make type(<url>) invalid in the attr() function (for now).

Computed (vs used) value of mix() functions
-------------------------------------------
  scribe: TabAtkins
  github: https://github.com/w3c/csswg-drafts/issues/12290

  fantasai: we have mix() that compute a weighted average from a list
            of items and percents
  fantasai: sometimes you can compute it to a final value, others you
            need to keep values distinct until used value time
  fantasai: we have two ways of simplifying we could take
  fantasai: one is to simplify as much as possible, including
            collapsing some items in the list
  fantasai: or we can just simplify each argument, preserving the
            number of items
  fantasai: so your computed value serialization would be either a
            single value, or a list with same number of items as
            previous. no halfway point like in the first option
  fantasai: currently we've specced the second option since it seems
            simpler, wanted opinions

  <emilio> I think whatever calc() does?
  <TabAtkins> calc() does first options

  astearns: doesn't first option lose info?
  astearns: how would I be able to look at list of values and get an
            average if I didn't know how many items where there?
  fantasai: each item has a weight, so your combined item has the
            combined weight
  fantasai: [gives example]
  astearns: ah, makes sense
  <lea> no opinion either
  astearns: slight preference for second, it preserves more of author
            intent
  astearns: and this is hopefully an edge case
  <lea> +1
  <kbabbitt> also lean slightly toward option 2

  <lea> What does min()/max() do? That might be a better precedent than
        calc() as it has multiple arguments too

  TabAtkins: Emilio commented in chat, seemed to lean option 1
  emilio: is there a reason to not be consistent with calc? max
          simplification?
  fantasai: calc() is hierarchical, you kinda combine things that are
            adjacent until they can't be combined any more
  fantasai: in mix(), if first and last can be combined, middle can't,
            you'll get a combined item that has to go first or last...
  emilio: I think we should simplify nested mix() if possible, and that
          bubbles
  fantasai: yes, if you can collapse a mix() into one value, you do
            that. this is if at least one item can't be combined
  emilio: ah kk, thought you were never simplifying
  fantasai: nah, just if one item can't be simplified
  TabAtkins: min()/max() simplify by combining items, so that's a
             precedent
  TabAtkins: from some feedback from smfr back in the day

  astearns: should we go with option 1 then, for precedent?
  fantasai: it's a little easier for min/max since you just drop some,
            while here you have to change values, but it's certainly
            doable
  astearns: so proposed is to simplify as much as possible (combining
            items when it can) for computed value
  fantasai: I feel like manipulating the result will be odd, but...

  RESOLVED: simplify as much as possible (combining items when it can)
            for computed value

Allow `if()` in descriptors
---------------------------
  scribe: fantasai
  github: https://github.com/w3c/csswg-drafts/issues/11941

  TabAtkins: We have if() function which allows conditional tests. But
             only works in properties, because substitution functions
             are only defined in that context
  TabAtkins: but there are good use cases for being able to use if()
             with global tests like MQ
  TabAtkins: It could be done by using conditional at-rules today
  TabAtkins: if() is a nice syntactic convenience.
  TabAtkins: Anders says it's doable as long as we're clear which tests
             can be used outside an element context
  TabAtkins: I agree, should be useful. Right now just style query and
             color scheme query that we'd have to turn off. But doable
  TabAtkins: So I suggest we accept this feature, spec text TBD
  <lea> +1, many upsides, little to no downsides

  fantasai: I think it makes sense, but rather than excluding we should
            use an allowlist
  TabAtkins: agree
  fantasai: like there's a lot of variable stuff we can't do outside of
            an element context

  emilio: relatedly, there might be descriptors (e.g. @font-face) ...
  emilio: even in the case in the OP, @font-face is exposed to the
          FontFace API
  emilio: It's fairly different from media case
  emilio: I'm not sure how we should expose these values when you ask
          for them
  emilio: Seems weird to do the substitution when you do the getter
  emilio: requires updating style information, it's a bit annoying
  TabAtkins: currently those return strings
  TabAtkins: so I think it's fine?
  TabAtkins: we'd have to define how substitutions are reflected
  TabAtkins: and I think it's fine to say that the OM doesn't reflect
             substitutions
  emilio: That means the programmatic functions need to accept these,
          and those are exposed to workers, which is annoying
  emilio: There's a lot of edge cases to think about
  emilio: And that's just the @font-face ones
  emilio: And then @page stuff might affect MQ?
  astearns: So seems useful thing, but complications to work through.

  lea: Do we need to exclude variables syntactically?
  lea: Could define that variables resolve to their initial value.
  lea: idk if web compatible
  lea: but as an author, that's what I would expect
  TabAtkins: I would say that those tests, not on allow list, would
             just resolve to false
  TabAtkins: that would potentially allow us to turn on some tests in
             the future

  fantasai: given all that, I think we should maybe defer this to
            later... I don't want us to spend tons of times going down
            the rabbit holes and delaying if() on elements
  fantasai: so maybe say this is a good idea, but for the next level.
            sounds like it'll be a bunch of work on the spec and impl
            side, for something that is technically doable right now in
            other ways
  <emilio> +1
  fantasai: so I worry if we batch it all together it'll be slower to
            get the core feature interoperable
  astearns: so we could resolve to accept it but punt to the next
            level. no more delay needed
  astearns: agree we shouldn't gate the existing if() on this
  <TabAtkins> +1
  PROPOSED: Work on this in the next level.

  RESOLVED: Work on this in the next level (in consideration of all the
            complications outlined above).

Clarify how `<attr-unit>` should be parsed
------------------------------------------
  github: https://github.com/w3c/csswg-drafts/issues/12479

  TabAtkins: Right now stuff for <attr-unit> production says "take this
             numerical attribute and pretend it's a pixel value"
  TabAtkins: says to parse it as a <number>
  TabAtkins: How literal are we about that? Is it a NUMBER token from
             Syntax? Or a <number> value from values?
  TabAtkins: the latter allows all the math functions
  TabAtkins: we need to clarify the spec in either direction
  TabAtkins: an earlier version of extended attr() was very
             specifically only looking for number tokens
  TabAtkins: for implementation simplicity reasons
  TabAtkins: it solved the use cases and was easy to do
  TabAtkins: as you allow more complicated things, it becomes more
             complicated
  TabAtkins: probably not worse than allowing more complexity in other
             values?
  TabAtkins: originally we added calc(), and then a jillion more math
             functions

  fantasai: this brings up interesting question
  fantasai: do we want to distinguish between CSS types (which should
            parse as full CSS) but have a different syntax for simple
            things
  fantasai: like "just a number"
  fantasai: for some core types like number... for string type we have
            a special type that doesn't do CSS parsing and just uses
            the attr value, so we already have cases for this
            distinction
  fantasai: so maybe this is a case where we distinguish
  fantasai: if you want CSS length values, use type(<length>), if you
            want CSS numbers, use type(<number.)
  fantasai: but if you just want a number interpreted as px, use this
            other syntax
  fantasai: and maybe that syntax is just the syntax we had before,
            with the keywords

  emilio: What is the behavior with custom properties?
  emilio: I would assume you ? parse calc() there
  emilio: consistency with that would be fair
  fantasai: This would mean any type() annotation is consistent with
            properties, but attr() would allow some extra things
  emilio: Agree that a raw number makes sense
  fantasai: Yeah, raw number with a unit appended, and a raw string,
            those are the special things we should be able to do

  astearns: We could take a resolution to define the type() parsing and
            have a new issue for extending the attr() for these raw
            types
  TabAtkins: This issue is about the unit keywords, not type() function
  TabAtkins: I'm happy with the proposal, where type keywords work for
             raw values, and anything with type() function is a CSS
             parse.
  TabAtkins: Probably means we want to add one more keyword for number,
             for raw numbers
  TabAtkins: currently we have raw-string, all the units, and %
  fantasai: So you're proposing raw-number?
  <TabAtkins> proposed: all the unit/% keywords are "raw number" - only
              a literal number. Add raw-number that's the same without
              a unit. All type() values are CSS-parsed, so
              type(<number>) allows calc()/etc
  PROPOSED: type() values are parsed just as in custom properties.
      Other attr() type values (units % etc) are literals.

  emilio: seems fair. I wish the keyword was just 'number'. It's a
          fairly common use cases, and 90% of use cases would be
          covered by it.
  emilio: raw-string is the default, so right now you don't have to
          type it
  TabAtkins: I'm fine with plain 'number'.
  PROPOSED: type() values are parsed just as in custom properties.
      Other attr() type values (units % etc) are literals. Add a
      'number' keyword for literal numbers.

  RESOLVED: type() values are parsed just as in custom properties.
            Other attr() type values (units % etc) are literals. Add a
            'number' keyword for literal numbers.

Using `if()` to do dark/light switching of image urls
-----------------------------------------------------
  github: https://github.com/w3c/csswg-drafts/issues/10577

  astearns: proposed to add a color-scheme() test to if()
  <TabAtkins> add color-scheme() test to if(), which tests the used
              color scheme on the element
  fantasai: is that cyclic?
  emilio: should we look at inherited value?
  TabAtkins: not cyclic because wouldn't work on color-scheme property
  TabAtkins: Looking at parent color-scheme wouldn't be useful.
  TabAtkins: It's not a complicated dependency, so should be
             straightforward.
  TabAtkins: we can make color-scheme() test fail in color-scheme
             property.
  emilio: There are typed custom properties as well...
  TabAtkins: We already have spec mechanisms to handle arbitrarily
             complex dependency chains accurately
  TabAtkins: this would add a dependency in
  TabAtkins: same as using a style test depends on custom property
  TabAtkins: thing to be concerned about is if likely that future
             language editions would create cycles that aren't already
             present
  TabAtkins: but I think it's safe to say that color-scheme won't
             depend on anything in the future

  fantasai: I defer to impls on implementability
  fantasai: but if we'll have special behavior on color-scheme prop to
            have it work, we should do the same as font-size/em
  fantasai: it also makes a somewhat useful thing - you can do the
            opposite color scheme of your parent
  astearns: Question of whether to add color-scheme() to container
            queries as well
  fantasai: if we're doing the hard one we should do the easy one :)
  <lea> +1 for color-scheme() on color-scheme resolving to parent
        color-scheme. I wish we had done that with var() too
  TabAtkins: Agree we should keep them in sync.

  PROPOSED: Add color-scheme() test to both @container queries and
      if(). Open question on what color-scheme() checks when in the
      color-scheme property.
  fantasai: Can we just resolve on the color-scheme() checks parent
            color-scheme?
  TabAtkins: unsure, let's leave it unresolved and discuss in other
             issue

  RESOLVED: Add color-scheme() test to both @container queries and
            if(). Open issue on what color-scheme() returns when used
            in color-scheme property.

*-interpolate() grammar can lead to confusing values
----------------------------------------------------
  github: https://github.com/w3c/csswg-drafts/issues/12348

  TabAtkins: kbabbitt noted that the interpolate puts the value we're
             interpolating first, then options, then a list of stops
  TabAtkins: he points out that this looks confusing because some of
             the options can be affecting output values
  <TabAtkins> background-color: color-interpolate(300px in hsl, 200px:
              red, 500px: green, 600px: blue);
  TabAtkins: but are placed next to the input value
  TabAtkins: 300px in hsl loos like it's modifying 300px
  TabAtkins: wanted to see if we could rearrange syntax to make it less
             confusing
  TabAtkins: oriol's suggestion was to put the options first
  TabAtkins: this puts the options close to the 'interpolate(' word
  <TabAtkins> color-interpolate(in hsl / 300px, 200px: red, 500px:
              green, 600px: blue)
  TabAtkins: uses a slash to separate the value out, to avoid syntactic
             ambiguity

  lea: Not a strong objection, but I would prefer to avoid mixing
       slashes vs commas, because CSS is inconsistent in how it
       handles it
  lea: can we use a keyword?
  <lea> color-interpolate(in hsl at 300px, 200px: red, 500px: green,
        600px: blue)

  fantasai: I haven't looked at this issue, but here's a wild idea. all
            the other values have a colon, what if you said `300px:
            in hsl`
  fantasai: so the first value is what we're interpolating, and options
            for the values

  oriol: Another point is, I believe we can just use a comma as a
         separator as there's no grammar overlap
  oriol: agree with Lea that mixing slash and comma is a bit weird. so
         I think we can just use a comma

  lea: Syntactically we want to distinguish prelude from list
  lea: So we want it to not look the same
  fantasai: I don't love having them in a different order. putting the
            value, the core thing you're acting on, last...
  <lea> `color-interpolate(at 300px in hsl, 200px: red, 500px: green,
        600px: blue)` seems ok to me?
  fantasai: it's also often not '300px', it's something complicated
            like a var()
  fantasai: it's one of the most important parts of the function, the
            first thing you'll think about
  astearns: Sounds like we don't have consensus on a direction yet.

  RESOLVED: Explore options for making a syntactic distinction between
            input and interpolation options.

Received on Wednesday, 24 September 2025 23:12:14 UTC