Skip to content

[css-backgrounds-4] Using logical keywords in background-position shorthand with multiple backgrounds #12132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
weinig opened this issue Apr 28, 2025 · 7 comments

Comments

@weinig
Copy link
Contributor

weinig commented Apr 28, 2025

When trying to implement support for background-position-block/background-position-inline and the new logical keywords added to <position> in CSS Values 5, I ran into an issue with the fact that background-position (and background) operate on lists of backgrounds.

To explain the issue, let's start with an existing example where you have the following usage:

<div id="test1" style="background-position: left bottom, right top;">...</div>

In this example, the following assertions hold:

assert(document.getElementById("test1").style.backgroundPositionX == "left, right");
assert(document.getElementById("test1").style.backgroundPositionY == "bottom, top");

Simple. When processing the shorthand, the parser split each background-position pair into an x and y component and created lists with them.

Now, consider a case using the new logical keywords along with the old physical keywords:

<div id="test2" style="background-position: left bottom, block-start inline-end;">...</div>

What should the following assertions be?

assert(document.getElementById("test2").style.backgroundPositionX == ???);
assert(document.getElementById("test2").style.backgroundPositionY == ???);
assert(document.getElementById("test2").style.backgroundPositionBlock == ???);
assert(document.getElementById("test2").style.backgroundPositionInline == ???);

Internally in the engine, we would probably be modeling this as with some placeholder value that tells the style building to not do anything for that specific value:

backgroundPositionX      == [ left,          <placeholder> ]
backgroundPositionY      == [ bottom,        <placeholder> ]
backgroundPositionBlock  == [ <placeholder>, block-start   ]
backgroundPositionInline == [ <placeholder>, inline-end    ]

But its not clear how that would serialize.

Two ideas, both from @fantasai, are:

  1. Allow lists to have empty values that take the place of the placeholder:
assert(document.getElementById("test2").style.backgroundPositionX == "left, ");
assert(document.getElementById("test2").style.backgroundPositionY == "bottom, ");
assert(document.getElementById("test2").style.backgroundPositionBlock == ", block-start");
assert(document.getElementById("test2").style.backgroundPositionInline == ", inline-end");
  1. Add an explicit defer keyword for the same purpose:
assert(document.getElementById("test2").style.backgroundPositionX == "left, defer");
assert(document.getElementById("test2").style.backgroundPositionY == "bottom, defer");
assert(document.getElementById("test2").style.backgroundPositionBlock == "defer, block-start");
assert(document.getElementById("test2").style.backgroundPositionInline == "defer, inline-end");

I prefer the explicitness of the defer keyword.

@SebastianZ
Copy link
Contributor

Adding an explicit keyword for this situation seems the best we can do in this case. Being explicit avoids confusion and makes it easier for authors to get to know about the reason behind this.

I just wonder if one keyword is enough for all cases in which logical and physical property names and values are mixed or if there are use cases requiring distinct keywords.

Sebastian

@weinig
Copy link
Contributor Author

weinig commented Apr 28, 2025

This really only applies to properties that operate on lists, I’m not sure it’s a general problem.

@SebastianZ
Copy link
Contributor

The issue isn't restricted to properties that operate on lists, as far as I understand. Though it applies to position properties that are restricted to either physical or logical positions.

Sebastian

@weinig
Copy link
Contributor Author

weinig commented Apr 29, 2025

I apologize but I don’t follow. Can you give me an example test case of a non list property showing this issue?

@SebastianZ
Copy link
Contributor

It looks like I expressed myself unclear. What I meant was that it is not the fact that background-position-{x|y|block|inline} are list properties but that they explicitly refer only to physical or logical directions, respectively, while they have a shorthand that (now) can take both physical and logical values.

So, even if background-position only took one position value instead of a list of values, we'd still face this problem.

I think, there is currently only one other case that'll need this, namely mask-position-{x|y}. Though they didn't make it into the spec. yet. And it'll come up again if we add more *-{x|y|block|inline} longhands for position properties.

Sebastian

@weinig
Copy link
Contributor Author

weinig commented Apr 29, 2025

I guess I don't quite understand why the hypothetical background-position that only takes on position value instead of a list of values would face a problem.

Taking the example above and trying both values separately, this is what I assume the results would be:

<div id="test3" style="background-position: left bottom;">...</div>
assert(document.getElementById("test3").style.backgroundPositionX == "left");
assert(document.getElementById("test3").style.backgroundPositionY == "bottom");
assert(document.getElementById("test3").style.backgroundPositionBlock == "");
assert(document.getElementById("test3").style.backgroundPositionInline == "");

and

<div id="test4" style="background-position: block-start inline-end;">...</div>
assert(document.getElementById("test4").style.backgroundPositionX == "");
assert(document.getElementById("test4").style.backgroundPositionY == "");
assert(document.getElementById("test4").style.backgroundPositionBlock == "block-start");
assert(document.getElementById("test4").style.backgroundPositionInline == "inline-end");

With the shorthand parser just picking one pair or the other to use.

Where would this break down?

@SebastianZ
Copy link
Contributor

My point was, the behavior should be the same in case of a hypothetical background-position that only takes one position and the background-position we have that operates on a list of positions. So, going with an explicit keyword, the result is this in both cases:

<div id="test3" style="background-position: left bottom;">...</div>
assert(document.getElementById("test3").style.backgroundPositionX == "left");
assert(document.getElementById("test3").style.backgroundPositionY == "bottom");
assert(document.getElementById("test3").style.backgroundPositionBlock == "defer");
assert(document.getElementById("test3").style.backgroundPositionInline == "defer");

and

<div id="test4" style="background-position: block-start inline-end;">...</div>
assert(document.getElementById("test4").style.backgroundPositionX == "defer");
assert(document.getElementById("test4").style.backgroundPositionY == "defer");
assert(document.getElementById("test4").style.backgroundPositionBlock == "block-start");
assert(document.getElementById("test4").style.backgroundPositionInline == "inline-end");

Though this discussion is moot, anyway, as we currently only have list-valued properties that are affected by this issue. So, apologies for the confusion!


My original point was to go with option 2, i.e. use an explicit keyword. Using an empty value might be confusing to authors and looks strange, especially if you have multiple of them. Also, it makes sense to distinguish the case of being set to a later-resolved value vs. not being set at all.


One question that just came to my mind is, what happens when you explicitly set the longhands to defer? E.g.

<div id="test5" style="background-position-x: defer, left; background-position-y: defer, bottom;">...</div>
assert(document.getElementById("test5").style.backgroundPosition == ???);
assert(document.getElementById("test5").style.backgroundPositionBlock == ???);
assert(document.getElementById("test5").style.backgroundPositionInline == ???);

One possible solution would be to fall back to the initial value, i.e.

assert(document.getElementById("test5").style.backgroundPosition == '0% 0%, left bottom');
assert(document.getElementById("test5").style.backgroundPositionBlock == '0%, defer');
assert(document.getElementById("test5").style.backgroundPositionInline == '0%, defer');

and

<div id="test6" style="background-position-block: defer, start; background-position-inline: defer, end;">...</div>

the result would be

assert(document.getElementById("test5").style.backgroundPosition == '0% 0%, block-start inline-end');
assert(document.getElementById("test5").style.backgroundPositionX == '0%, defer');
assert(document.getElementById("test5").style.backgroundPositionY == '0%, defer');

Also note that in your initial examples you had a little error. The logical longhands don't use the block- and inline- prefixes for the values (to avoid redundancy). So, when setting background-position: block-start inline-end; on the element the result should be

assert(document.getElementById("test2").style.backgroundPositionBlock == "start");
assert(document.getElementById("test2").style.backgroundPositionInline == "end");

not

assert(document.getElementById("test2").style.backgroundPositionBlock == "block-start");
assert(document.getElementById("test2").style.backgroundPositionInline == "inline-end");

Sebastian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants