Skip to content

[css-anchor-position][other] Handling popover default styles #10258

Closed

Description

@tabatkins

HTML defines some UA styles for popover elements https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3:

[popover] {
  position: fixed;
  inset: 0;
  width: fit-content;
  height: fit-content;
  margin: auto;
  border: solid;
  padding: 0.25em;
  overflow: auto;
  color: CanvasText;
  background-color: Canvas;
}

These styles center the element in the page, which is a useful default display for a popover.

However, popover was always designed with the intention that it would also be able to interop with Anchor Positioning, and position itself relative to its invoker (this is the whole reason the implicit anchor name exists).

Unfortunately, if you then do the simplest, most obvious styling, like:

[popover] {
  top: anchor(bottom);
  left: anchor(left);
}

or even

[popover] {
  inset-area: bottom span-right;
}

neither of these work correctly! Both of them end up interacting with the default inset: 0; from the UA styles, and behaving wrong as a result (stretching oddly, and/or not responding to scrolling correctly). Authors have to learn to manually set inset: auto; in these cases to reset things, and it's very easy to forget. Worse, it's very hard to realize what's happening, as the IMCB isn't visible in the page and not well-indicated in DevTools, either.

(Also, it uses margin-based centering, which has to be reset too, but we're finally at the point where we can depend on place-self: center working for abspos.)

In summary, we have two distinct styles we need:

  • when not anchoring, inset: 0; place-self: center;
  • when anchoring, inset: auto; place-self: normal; (aka the initial values)

I don't have a great solution for this! It would be nice if we could somehow only apply those "center in the screen" styles when the element isn't using anchoring, but the only way to detect that is by examining the styles themselves. Maybe we need to indicate that the popover will be anchored in the HTML syntax? Other solutions appreciated.

Or perhaps we could have a value, maybe in align/justify-self, which conditionally applies the "right" behaviors when something is anchored vs not? If it can just resolve to center vs normal, and trigger the "auto insets go to 0" behavior that anchor-center does, it would Just Work. I think this is defensible as a reasonable fallback for a lack of anchors? The UA stylesheet would then look like:

[popover] {
  position: fixed;
  place-self: bikeshed-center-when-unanchored;
  /* border, etc.
}

Or maybe this is a condition that can trigger position fallback, somehow - if your anchors don't exist, fallback to the next style? I imagine associating the position-visiblity keywords with individual try-options, indicating that they'll be used under those conditions. Then the UA stylesheet would look like:

[popover] {
  position: fixed;
  position-try-options: anchors-valid --ua-screen-centered;
}
@position-try --ua-screen-centered {
  inset: 0;
  place-self: center;
}

I kinda like this last one a lot? It seems worthwhile for authors in other cases, and is the most flexible. It's also the least intrusive when you don't think about it - if you're just applying some anchor styles, it'll do nothing, which is what you want, and if you do end up specifying some position-try stuff it'll get silently overridden without causing lingering issues.

/cc @mfreed7

Activity

  1. tabatkins commented on Apr 26, 2024

    @tabatkins
    MemberAuthor

    Maybe for that last one, the syntax would be just supplying a position-try block name to position-visibility, like position-visibility: anchors-valid --ua-screen-centered;; if you don't supply a position-try name, it does the default "super-hide it" behavior.

  2. nt1m commented on Apr 29, 2024

    @nt1m
    Member

    I'm surprised inset-area: bottom span-right; doesn't work, I was expecting inset-area to reset insets for all 4 sides.

  3. tabatkins commented on Apr 30, 2024

    @tabatkins
    MemberAuthor

    It was never defined to act like a shorthand for the other insets, no. (That would be a slightly strange design, tho not unworkable perhaps.) You usually want auto insets for inset-area, but not necessarily always.

    Even if it did, tho, the alignment would still be wrong; inset-area wants normal while the non-anchored popup wants center.

  4. fantasai commented on May 12, 2024

    @fantasai
    Collaborator

    Both of them end up interacting with the default inset: 0; from the UA styles, and behaving wrong as a result (stretching oddly, and/or not responding to scrolling correctly).

    Wait, explain this bit? Why would the inset-area one have this effect? The spec sets auto insets to zero already whenever inset-area isn't none so... if that' breaks things then it's very broken?

  5. o-t-w commented on Jan 20, 2025

    @o-t-w

    I don't find centering by default compelling. I'd usually use a dialog element for use-cases that require centering in the viewport. Most popover use-cases require positioning the popover in relation to the anchor. inset: auto; is a better default.

  6. jakearchibald commented on Jul 9, 2025

    @jakearchibald
    Contributor

    When I used position-area: top, it appeared "not working" until I figured out I needed to also set margin: 0. position-area is a kinda magic "we'll just do the right thing for you" feature, so it was kinda frustrating that it came with this confusing gotcha.

    I don't have any solutions to recommend though. Maybe using position-area or anchor() in any position value, activates a pseudo-class that can only be used in the UA stylesheet (to avoid loops in author code), which is in-turn used to set margin: 0 in the UA stylesheet. Ugh.

  7. jamesnw commented on Jul 9, 2025

    @jamesnw

    Because the UA styles for popover contain margin: auto, would it be possible to say that margin: auto resolves to 0 when the popover element is using anchor positioning?

    We could re-use the conditions from Compensates for scroll. "If a popover has a default anchor box and an anchor reference to its default anchor box, the auto keyword for margin resolves to 0."

    I was unable to find a specification for margin: auto, so I'm not sure what other impacts there might be.

  8. yisibl commented on Jul 9, 2025

    @yisibl
    Contributor

    When I used position-area: top, it appeared "not working" until I figured out I needed to also set margin: 0. position-area is a kinda magic "we'll just do the right thing for you" feature, so it was kinda frustrating that it came with this confusing gotcha.

    This used to be in Chrome, where you didn't need margin: 0, and then the behavior changed. Please refer to: https://issues.chromium.org/issues/374574260

  9. tabatkins commented on Jul 21, 2025

    @tabatkins
    MemberAuthor

    Okay, @fantasai and I are giving this issue another shot, because it continues to be a constant souce of confusion for authors, even very experienced ones. See e.g. #12466

    Recapping the issue:

    • The HTML UA stylesheet for [popover] defaults to styling the element in a “non-anchored” way - it's fixpos and centered on the screen. This is perfectly fine as a default.
    • The popover API also gives the popover element an implicit anchor element, so anchor positioning can work. But when authors try to use that, by setting position-area: bottom span-left or something, it doesn't work. This is because the “centered fixpos” styles don't play nicely with anchorpos styling and defaults. To get things to work, authors have to remember to unset margins back to 0.

    Our goal is to square this circle, and make it so that authors doing the intuitive thing get the intuitive result, if possible. We think we've hit on a solution.

    Proposal

    Add a new dialog value to align/justify-self. (Better name potentially TBD, but we think this one could work as-is.) This value responds to position-area: if position-area is set to a non-none value, it resolves just like align/justify-self:normal. But if position-area is none (initial value), then it triggers the position-area additional behavior (resolving auto insets to 0, etc), but acts like align/justify-self: center. (The normal value already responds to position-area, so this dependency has strong precedent.)

    The HTML UA stylesheet can then simplify, replacing all the sizing/aligning properties for popovers:

    [popover] {
      position: fixed;
      place-self: dialog; /* new */
      /* deleted
      X inset: 0;
      X width: fit-content;
      X height: fit-content;
      X margin: auto;
      */
      border: solid;
      padding: 0.25em;
      overflow: auto;
      color: CanvasText;
      background-color: Canvas;
    }

    By default, this centers the element in the viewport and sizes it to its content, as today. If an author sets position-area, tho, it'll automatically work as expected - position-area: bottom span-right; will “attach” it to the popover invoker.

    This also handles some other cases more elegantly, we think. For example, an author setting place-self: stretch today would also not see any effect, because the default styles explicitly set width and height; after this change, it'll actually stretch.

    An alternative we considered was a new value for position-area which would behave just like span-all except resolving normal alignment to center instead of anchor-center. Both options would get us similar behavior, but this felt more like an alignment problem than a position-area problem.

    CC @kizu @jakearchibald

  10. una commented on Jul 23, 2025

    @una
    Contributor

    YES please fix this, it is a major footgun! Though I think the real issue is for specifically anchor positioned elements, not just general [popover] elements. When you anchor position an element, that's essentially by definition saying you want to position it relative to its anchor and not the viewport (current UA style behavior that gets in the way). Some [popover] elements won't be anchored and thus aren't affected by this as much. The anchored popovers are where this really comes into play and causes so much frustration.

    Additionally, for semantic purposes, a dialog might also be anchored. Is there a way to resolve on a solution for specifically anchored elements?

  11. tabatkins commented on Jul 25, 2025

    @tabatkins
    MemberAuthor

    @una pointed out in private chat that this defaults issue extends to dialogs in general, not just popovers; I'd missed that, despite looking at the styles for dialogs in general at the same time.

    It stems from the same basic issue: it's using margins to center in the inline axis, which mispositions it when you use anchor positioning. See this basic example:

    <!DOCTYPE html>
    <dialog open id=foo>foo</dialog>
    <p id=bar>bar</p>
    <style>
    body { background: linear-gradient(to right, white 49.5%, black 0 50.5%, white 0);
    #bar { width: 100px; height: 50px; border: thick dotted; anchor-name: --bar;}
    #foo { position-anchor: --bar; position-area: bottom right; background: lime; /margin: 0;}
    </style>

    The big black bar indicates the center of the page - the dialog is centered in the bottom-right position-area cell, which puts it far away from its anchor and looking oddly off-center as well. It also ends up positioned identically if you instead open it as a modal, and scrolls with its anchor (because that's how anchored fixpos is supposed to work!)

    Luckily, the modal dialog behavior is essentially identical to the popover behavior - fixpos, centered in the screen. The non-modal dialog is a bit different - abspos, centered inline, auto-positioned block. This works out nicely! We can just add another behavior for dialog when it's abspos rather than fixpos, so it does not resolve the block insets to 0 and sticks with normal alignment in the block axis; by default, then it'll use the static position, as specified today. That would let us simplify the stylesheets further:

    dialog {
      position: absolute;
      place-self: dialog; /* new */
      /* deleted
      X inset-inline-start: 0; inset-inline-end: 0;
      X width: fit-content;
      X height: fit-content;
      X margin: auto;
      */
      border: solid;
      padding: 1em;
      background-color: Canvas;
      color: CanvasText;
    }
    dialog:modal {
      position: fixed;
      overflow: auto;
      /* deleted
      X inset-block: 0;
      */
      max-width: calc(100% - 6px - 2em);
      max-height: calc(100% - 6px - 2em);
    }
    
    /* ...irrelevant stuff that stays as-is... */
    
    [popover] {
      position: fixed;
      place-self: dialog; /* new */
      /* deleted
      X inset: 0;
      X width: fit-content;
      X height: fit-content;
      X margin: auto;
      */
      border: solid;
      padding: 0.25em;
      overflow: auto;
      color: CanvasText;
      background-color: Canvas;
    }

    leaving us with just:

    dialog {
      position: absolute;
      place-self: dialog;
      border: solid;
      padding: 1em;
      background-color: Canvas;
      color: CanvasText;
    }
    dialog:modal {
      position: fixed;
      overflow: auto;
      max-width: calc(100% - 6px - 2em);
      max-height: calc(100% - 6px - 2em);
    }
    
    /* ...irrelevant stuff that stays as-is... */
    
    [popover] {
      position: fixed;
      place-self: dialog; 
      border: solid;
      padding: 0.25em;
      overflow: auto;
      color: CanvasText;
      background-color: Canvas;
    }

    And now, using position-area on a dialog will Just Work (once you also set position-anchor, since it doesn't get an implicit anchor)

  12. nt1m commented on Jul 25, 2025

    @nt1m
    Member

    Not a fan of a special "dialog" value. Is it something that could benefit non-dialog use cases? Is it top layer specific? Names should reflect that ideally.

  13. 80 remaining items

  14. astearns commented on Nov 10, 2025

    @astearns
    Member

    RESOLVED: Auto margins are disabled if align-self is set to anchor-center

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions