Skip to content
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

[css-position-3] Incorrect handling of auto inset properties in absolute positioning #11242

Open
gitspeaks opened this issue Nov 19, 2024 · 21 comments
Labels
css-position-3 Current Work

Comments

@gitspeaks
Copy link

Spec References

  1. 3.5.1. Resolving Insets: the “Inset-Modified Containing Block”

    "If only one inset property in a given axis is auto, it is set to zero."

  2. 3.1. Box Insets

The initial value of top, right, bottom, or left is: auto.

  1. 4. Absolute Positioning Layout Model

Absolute positioning not only takes a box out of flow, but also lays it out in its containing block (after the final size of the containing block has been determined) according to the absolute positioning layout model:

  1. First, its inset-modified containing block is calculated, defining its available space.
  2. Next, its width and height are resolved against this definite available space, as its preferred size

Scenario

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      #parent {
        position: relative;
        width: 200px;
        height: 200px;
        border: 1px solid black;
      }

      #child {
        position: absolute;
        background-color: yellow;
        top:0;
        right:0;
      }
    </style>
  </head>
  <body>
    <div id="parent">
      <div id="child">
        BOX
      </div>
    </div>
  </body>
</html>

Expected Behavior

According to the spec, the inset properties left and bottom, being exclusively auto in their respective axes, should compute to 0. This means:

  1. With top and right explicitly set to 0, the #parent div defines an inset of 0 on all sides, meaning the inset-modified containing block will match the dimensions of the absolute-position containing block (e.g #parent div).

  2. The #child div should size itself to cover the maximum available space, filling the #parent div entirely.

Actual Behavior

In both Chrome and Firefox:

  1. The #child div sizes itself to fit its inline content.
  2. Its position is aligned to the top-right edge of the #parent div padding edge.

This behavior contradicts the specification, as the unset inset properties (left and bottom) are resolved to non-zero values.

Note:

  • This behavior persists even when explicitly setting left and bottom to auto, instead of relying on their implicit initial value of auto.

  • Explicitly setting all inset properties (top: 0; right: 0; left: 0; bottom: 0;) causes the #child div to correctly cover the #parent div, showing that the behavior works as expected when no auto values are involved.

@gitspeaks gitspeaks changed the title Incorrect handling of auto inset properties in absolute positioning [css-position-3] Incorrect handling of auto inset properties in absolute positioning Nov 19, 2024
@Loirooriol
Copy link
Contributor

The #child div should size itself to cover the maximum available space

No. As per https://www.w3.org/TR/css-position-3/#abspos-auto-size

@Loirooriol Loirooriol added the css-position-3 Current Work label Nov 19, 2024
@gitspeaks
Copy link
Author

No. As per https://www.w3.org/TR/css-position-3/#abspos-auto-size

Otherwise
Its automatic size is its fit-content size.

I'm not sure whether this should be interpreted as a contradiction or a specialization of point 2 in 4. In any case, it doesn't explain why the #child div aligns to the top-right edge of the inset-modified containing block or what the size of the inset-modified containing block actually is.

@Loirooriol
Copy link
Contributor

it doesn't explain why the #child div aligns to the top-right edge of the inset-modified containing block

Mm, yeah. So I think we have to options:

  • Keep saying that a lone auto inset resolves to zero. But then we need a normal self-alignment to resolve to either start or end, whichever side has the non-auto inset.
  • Stop saying that a lone auto inset resolves to zero. Instead, resolve the auto inset to whatever value makes the size of the inset-modified containing block match the fit-content size of the abspos. This fit-content size shrinks-to-fit into the original containing block size minus the non-auto inset, or equivalently, into a tentative inset-modified containing block with the auto offset resolved to 0.

The difference is observable when setting the self-alignment to e.g. center. All browsers seem to be doing the 2nd option.

@gitspeaks
Copy link
Author

gitspeaks commented Nov 19, 2024

Stop saying that a lone auto inset resolves to zero. Instead, resolve the auto inset to whatever value makes the size of the inset-modified containing block match the fit-content size of the abspos. This fit-content size shrinks-to-fit into the original containing block size minus the non-auto inset, or equivalently, into a tentative inset-modified containing block with the auto offset resolved to 0.

This feels a bit complex to me. Introducing a dependency where the dimensions of the inset-modified containing block (IMCB) depend on the dimensions of the absolutely positioned box (abspos) adds a layer of interaction that doesn’t exist in the current definition. If the abspos overflows the absolute-position containing block, the IMCB would overflow as well, and its position becomes unclear—especially with mixed auto and non-auto inset values.

What I propose is:

  1. Stick with the current behavior of auto resolving to 0.
  2. Maintain the current definition of the IMCB as an offset from the edges of the absolute-position containing block.
  3. Update section 4 to clarify:
  • Point 2: Width and height are resolved against the definite available space... when all inset values are non-auto.
  • Add a new point (5): Define the positioning of the abspos within the IMCB:
    • If one inset value in an axis is auto, align the abspos to the IMCB edge corresponding to the inset with the non-auto value.
    • If both inset values for an axis are either auto or non-auto, align the abspos to the IMCB edge as follows:
      • top: auto; bottom: auto; → align to the top edge.
      • left: auto; right: auto; → align to the left edge.

What do you think?

@Loirooriol
Copy link
Contributor

adds a layer of interaction that doesn’t exist in the current definition

Well, it does exist in the CSS2 definition, and CSS Position is supposed to be backwards compatible with it.

Width and height are resolved against the definite available space... when all inset values are non-auto

I think place-self: stretch should stretch an automatic size.

Define the positioning of the abspos within the IMCB

This needs to be handled via CSS Align. It could only be about normal alignment resolving according to that logic. Which is the 1st option I described above.

@gitspeaks
Copy link
Author

Well, it does exist in the CSS2 definition, and CSS Position is supposed to be backwards compatible with it.

Could you include a link to the relevant CSS2 section?

The difference is observable when setting the self-alignment to e.g. center. All browsers seem to be doing the 2nd option.

Admittedly, I'm not entirely clear on this. I tested using both align-self: center; and justify-self: center; on the #child div, but neither seemed to have any effect. Could you clarify how this is supposed to work in this context?

@Loirooriol
Copy link
Contributor

Could you include a link to the relevant CSS2 section?

https://drafts.csswg.org/css2/#abs-non-replaced-width resolves left: auto after the size:

  1. left and width are auto and right is not auto, then the width is shrink-to-fit. Then solve for left

I tested using both align-self: center; and justify-self: center; on the #child div, but neither seemed to have any effect

Yes, that's why I said that browsers seem to use the 2nd option. If they were doing the 1st option then the abspos would be aligned within the alignment container, which is the inset-modified containing block in this case.

@gitspeaks
Copy link
Author

https://drafts.csswg.org/css2/#abs-non-replaced-width

This seems like a pretty different approach compared to the current spec. In the CSS2 model, the inset properties are basically just offsets from the edges of the containing block that position the abspos element but there’s no mention of top/bottom in this context.

In the current spec, though, the inset properties define the size of the reduced containing block—the inset-modified containing block (IMCB)—and the abspos element is positioned inside it. But figuring out the exact position of the abspos element within the IMCB is a separate issue.

That said, the current spec definitely needs to stay compatible with the expected CSS2 outcomes. But I’m not quite following your suggestion:

resolve the auto inset to whatever value makes the size of the inset-modified containing block match the fit-content size of the abspos. This fit-content size shrinks-to-fit into the original containing block size minus the non-auto inset, or equivalently, into a tentative inset-modified containing block with the auto offset resolved to 0.

How do you figure out the abspos element’s initial position to base the IMCB offsets on? The inset properties are supposed to define the offsets of the IMCB, not the abspos itself, right? It feels a bit circular—can you clarify?

@Loirooriol
Copy link
Contributor

there’s no mention of top/bottom

https://drafts.csswg.org/css2/#abs-non-replaced-height

It feels a bit circular—can you clarify?

  1. Resolve auto to 0px to get a tentative IMCB
  2. Size the abspos into that tentative IMCB, treating an automatic size as fit-content
  3. Get rid of the tentative IMCB
  4. Resolve auto to whatever makes the actual IMCB be as big as (2)
  5. Assert: sizing the abspos into the actual IMCB from (4), treating an automatic size as fit-content, produces the same size as (2)

@gitspeaks
Copy link
Author

  1. The size of the IMCB does not determine the actual positioning of the abspos element, which is the core issue here. I don’t see how the steps you outlined address this problem.

  2. Honestly, it’s not clear what the IMCB is supposed to achieve. What’s its purpose? Especially if all browsers are already aligned with the CSS2 model, I’m struggling to see why this change is necessary.

@gitspeaks
Copy link
Author

gitspeaks commented Nov 20, 2024

In the current spec, the inset property is used to establish a frame called the inset-modified containing block (IMCB) within the containing block, and the positioned element (abspos) is placed in that frame. According to the layout model, the abspos element is sized to the dimensions of the IMCB, eliminating the need to explicitly define the 'position' of the abspos within the IMCB.

However, the same spec section states, by implication, that if the abspos element has an inset property value of auto, it is sized to fit-content. This means the abspos box can be smaller than the IMCB, resulting in a scenario where the abspos position within the IMCB frame becomes undefined.

Example:

Consider the following code. Any of the images below could represent a legitimate rendering:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      #parent {
        position: relative;
        width: 200px;
        height: 200px;
        border: 5px solid black;
      }

      #child {
        position: absolute;
        background-color: yellow;
        top: 0;
        right: 0;
        /* bottom: auto → 0 (spec 3.1. Box Insets) */
        /* left: auto → 0 (spec 3.1. Box Insets) */
      }
    </style>
  </head>
  <body>
    <div id="parent">
      <div id="child">BOX</div>
    </div>
  </body>
</html>

1
2
3
4

To resolve this, additional meaning would need to be added to auto inset values to determine the abspos position within the IMCB. This dual meaning increases complexity and does not align with the CSS2 spec, where the inset properties are simply defined as offsets of the abspos from the containing block.

I propose reverting the current inset definition to align with the CSS2 spec, which is also the behavior currently implemented across browsers.

@Loirooriol
Copy link
Contributor

The size of the IMCB does not determine the actual positioning of the abspos element

The alignment within the IMCB is determined by CSS Align as I already said.

Honestly, it’s not clear what the IMCB is supposed to achieve.

I think it was added as part of the CSS Position overhaul of the CSS2 logic, which was trying to simplify the logic in a way that properly handles direction, writing mode, alignment, etc. But ask the editors who added it.

the abspos element is sized to the dimensions of the IMCB

Not in general.

This means the abspos box can be smaller than the IMCB

Yes. This doesn't require auto insets.

additional meaning would need to be added to auto inset values to determine the abspos position

No, if the IMCB has a different size than the abspos, you just use CSS Align properties. At most you can affect the resolution of a normal self-alignment.

I propose reverting the current inset definition to align with the CSS2 spec

CSS2 doesn't handle lots of modern things, like alignment.

@gitspeaks
Copy link
Author

The alignment within the IMCB is determined by CSS Align as I already said.

But it doesn't work in Chrome or Firefox. We already discussed this (align-self: center; and justify-self: center;), so what's the point of bringing it up again?

This means the abspos box can be smaller than the IMCB
Yes. This doesn't require auto insets.

Yet another positioning issue remains unaddressed.

No, if the IMCB has a different size than the abspos, you just use CSS Align properties. At most you can affect the resolution of a normal self-alignment.
But align properties don’t work. How are we supposed to handle back-compatibility in this case?

CSS2 doesn't handle lots of modern things, like alignment.

We are talking about the very essence of positioning in CSS. Do you really think it’s reasonable to redefine the entire semantics of absolute positioning and expect adoption? I’ve read every point in the CSS2 spec you referenced—it’s mathematically precise. The information in the current spec doesn’t even come close to that level of detail.

@Loirooriol
Copy link
Contributor

But it doesn't work in Chrome or Firefox

No, it works, as you can see with non-auto insets (use Firefox Nightly). It's just no-op with auto insets, i.e. the IMCB has the same size as the abspos.

Yet another positioning issue remains unaddressed.

No, see above

Do you really think it’s reasonable to redefine the entire semantics of absolute positioning and expect adoption?

Yes, as long as it's backwards compatible.

it’s mathematically precise

Being precise in the scope of CSS2 is useless if it doesn't handle the interaction with new CSS features.

@gitspeaks
Copy link
Author

No, it works, as you can see with non-auto insets (use Firefox Nightly). It's just no-op with auto insets, i.e. the IMCB has the same size as the abspos.

What do you mean by Firefox Nightly? It doesn’t work in the most recent version of Chrome!

I feel like we’re talking past each other... Is the 'current spec' about a 'potential future CSS'?

Which spec is considered authoritative for what is currently supported by browsers?

@Loirooriol
Copy link
Contributor

Loirooriol commented Nov 20, 2024

What do you mean by Firefox Nightly?

https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly

It doesn’t work in the most recent version of Chrome!

It works, see some examples in servo/servo#34226

Is the 'current spec' about a 'potential future CSS'?

Sometimes? Not sure if you have noticed that CSS Position 3 is a Working Draft, it's not a Recommendation yet.

The CSS Snapshot 2024 classifies it as a "module with rough interoperability"

Although the following modules have been widely deployed with rough interoperability, their details are not fully worked out or sufficiently well-specified and they need more testing and bugfixing. We hope to incorporate them into the official definition of CSS in a future snapshot.

@gitspeaks
Copy link
Author

It works, see some examples in servo/servo#34226

This doesn't work in Chrome, which is the entire point of this issue.

<!DOCTYPE html>
<div style="position: relative; width: 100px; height: 100px; border: 3px solid">
  <div style="position: absolute; align-self: center; justify-self: center; background: cyan; height: 25px; width: 25px; inset: auto 0 0 auto"></div>
</div>

chrome

The CSS Snapshot 2024 classifies it as a "module with rough interoperability"

Understood.

In the meantime, I think I’ll stick to what has been tested and proven to work.

css2

@Loirooriol
Copy link
Contributor

No it's not the entire point. You want to make CSS Align properties do nothing on abspos because they didn't exist in CSS2 and you wrongly claim that browsers don't support them. You are of course free to stick to CSS2 for whatever you are doing, but here we are trying to decide how the language will evolve. This conversation is not productive so I will stop.

@gitspeaks
Copy link
Author

gitspeaks commented Nov 20, 2024

you wrongly claim that browsers don't support them

@gitspeaks wrote:

But it doesn't work in Chrome or Firefox. We already discussed this (align-self: center; and justify-self: center;), so what's the point of bringing it up again?

Please don’t misrepresent what I said. I provided two examples in this issue that demonstrate the CSS Align properties don’t work in the most recent browsers under the conditions described in this issue.

This conversation is not productive so I will stop.

I agree. The conversation should focus on refining the definition so that both CSS2 and CSS3 users can use absolute positioning as described in their respective specifications. This discussion is not about what is available in 'Nightly.'

but here we are trying to decide how the language will evolve.

I have demonstrated very clearly a deficiency in the spec regarding absolute positioning. If CSS Align properties provide a resolution in the future, so be it. However, the larger issue here is that we now have two completely different models for absolute positioning running concurrently. That, my friend, is bound to create significant confusion for developers. Mark my words.

@gitspeaks
Copy link
Author

gitspeaks commented Nov 20, 2024

For anyone involved in "trying to decide how the language will evolve", please consider how absolute positioning has been taught to millions of developers up until today:

MDN introductory course "Learn to style HTML using CSS"

"notice that the position of the element has changed. This is because top, bottom, left, and right behave in a different way with absolute positioning. Rather than positioning the element based on its relative position within the normal document flow, they specify the distance the element should be from each of the containing element's sides. So in this case, we are saying that the absolutely positioned element should sit 30px from the top of the "containing element" and 30px from the left."

Source: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning

@gitspeaks
Copy link
Author

@fantasai , @tabatkins could you please share your perspective on this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-position-3 Current Work
Projects
None yet
Development

No branches or pull requests

2 participants