Skip to content

[selectors-4] Introduce :popover-open pseudo class #8637

Closed

Description

@mfreed7

We recently resolved to add general-purpose :open and :closed pseudo classes, which apply to "things that can open and close". While not defined in CSSWG, the stated intention was to include things like <details>, <select>, <dialog>, and Popover.

@jakearchibald recently raised the issue that in some cases, this is ambiguous, because there are multiple "ways" that something can be open or closed, and both might apply at the same time. Take, as an example, <details popover>, which can be "open" as a popover, but "closed" as a details, or vice versa. There's no way for these to be disambiguated on the basis of :open or :closed alone.

It is a bit unclear whether fullscreen elements should be included as "something that can open and close", but it's possible. That allows the possibility of a fullscreen <details> element which can be both open and closed. But fullscreen elements have a dedicated pseudo, :fullscreen, which allows one to disambiguate that state of openness vs. others.

It would seem that we need another such pseudo for popovers, because any element can also be a popover, and can therefore run into this dual-states problem. We discussed this in OpenUI, and proposed :popover as a pseudo class that applies only to popovers that are open (as a popover). That would allow developers to do:

details[popover]:closed:popover {
  /* styles for a non-expanded details that is open as a popover */
}

Activity

  1. dbaron commented on Mar 23, 2023

    @dbaron
    Member

    One thing I suggested in the OpenUI discussion is that :open and :closed could be for anything where the open/closed state is related to the element semantics, but that things like popover and fullscreen (which can be applied to any element) should have separate pseudo-classes to distinguish them and avoid this problem of unexpected intersections.

  2. mfreed7 commented on Mar 23, 2023

    @mfreed7
    ContributorAuthor

    One thing I suggested in the OpenUI discussion is that :open and :closed could be for anything where the open/closed state is related to the element semantics, but that things like popover and fullscreen (which can be applied to any element) should have separate pseudo-classes to distinguish them and avoid this problem of unexpected intersections.

    This also seems like a reasonable suggestion. @jh3y do you know of any popover use cases that would be harmed by not having access to a :closed pseudo class? It seems like :not(:popover) would generally work instead, but just checking.

  3. jh3y commented on Mar 23, 2023

    @jh3y
    Contributor

    Generally up until now you'd likely do something like [popover]:not(:open) so don't see an issue. I remember a while back it was not(:top-layer) in a demo 👀

  4. tabatkins commented on Mar 23, 2023

    @tabatkins
    Member

    :open and :closed could be for anything where the open/closed state is related to the element semantics, but that things like popover and fullscreen (which can be applied to any element) should have separate pseudo-classes to distinguish them

    That distinction seems pretty reasonable to me, yeah.

  5. jakearchibald commented on Mar 27, 2023

    @jakearchibald
    Contributor

    @dbaron @tabatkins should :open apply to popovers as well as :popover, or is :popover instead of :open?

    I think I'm leaning towards :open not applying to popover, as it also avoids difficult questions around :open and :closed applying at the same time, due to multiple definitions of open/closed.

    So if an element type can open/close, then it uses :open and :closed, whereas concepts that apply to multiple element types, such as popovers and fullscreen, get their own pseudoclass.

  6. jh3y commented on Mar 27, 2023

    @jh3y
    Contributor

    My understanding was that :popover replaces :open to detect whether a popover is in the top layer.

    The only issue I see is "Does :popover communicate that state well?". But, then I think it's likely fine as it kinda falls in with :modal/:fullscreen. Hopefully, it won't cause confusion.

  7. keithamus commented on Mar 27, 2023

    @keithamus
    Member

    Could all elements that are in the top-layer have a :top-layer pseudo? That would be applicable to dialogs, modals, and popovers, right? I suppose one counterpoint is that it does not clear up the ambiguity between an open dialog and an open dialog popover?

  8. jh3y commented on Mar 27, 2023

    @jh3y
    Contributor

    That's what it was at one point last year. There was a :top-layer pseudo for detecting when a popover was "popped" 😅

    But, I think it was removed for naming reasons.

  9. mfreed7 commented on Mar 27, 2023

    @mfreed7
    ContributorAuthor

    So we might consider :overlay as a pseudo class for things in the top layer, given that we decided to call the CSS property overlay. But I think that's a question for a different issue.

    I'm in favor of a) adding :popover for (only) popovers in the open state, and b) removing support for :open and :closed for popovers entirely. It feels like the definition for :open and :closed really refers specifically to specific elements that can open and closed, and not any element that can be made to be open or closed through a separate attribute or Javascript method. So that rules out popover and fullscreen.

  10. jh3y commented on Mar 27, 2023

    @jh3y
    Contributor

    I still find :overlay to be an interesting choice that potentially gets muddled up with ::backdrop.

    Sometimes I wonder if the appropriate pseudo is one we discussed before with :show/n/ing. Something like details is opened/closed. But, we have popovers that can be shown or not shown (:shown, :not(:shown)).

  11. mfreed7 commented on Mar 27, 2023

    @mfreed7
    ContributorAuthor

    Sometimes I wonder if the appropriate pseudo is one we discussed before with :show/n/ing. Something like details is opened/closed. But, we have popovers that can be shown or not shown (:shown, :not(:shown)).

    I think the problem is that, due to the issues we've discussed above, it needs to be specific to the API. So it should have "popover" in the name. And akin to :fullscreen for the "fullscreen" API, it seems most parallel to use :popover. Another alternative is something like :popover-open but that's verbose and seemingly unnecessary. If the popover is "closed" but made visible via display:block or something, I'd say it really isn't a popover any longer, it's just an element, so it still makes sense for that not to match :popover.

  12. 13 remaining items

  13. annevk commented on Apr 3, 2023

    @annevk
    Member

    HTML side of this landed: whatwg/html#9077.

  14. destradaxcheckapp commented on Apr 2, 2024

    @destradaxcheckapp

    Hi @mfreed7

    I have a working scenario where I need this pseudo class and is also related to your comment about the toggle events not being cancelable when hiding popovers.

    I created a React component to encapsulate a popover and I need it to be descriptively controlled instead of imperatively.

    My Popover component receives an open property, renders a

    <dialog popover="auto" ref={dialogRef}>{children}</dialog>

    and shows (dialogRef.showPopover()) or hides (dialogRef.hidePopover()) the popover based on the open property.

    1. I cannot intercept and cancel (event.preventDefault()) the toggle event to rely on the consumer of my component changing the open property; the most I can do is let the consumer know that the popover was closed.

    2. Popovers can be light-dismissed by a user action (clicking outside or pressing the Escape key), so I need to first check if the popover is still visible before attempting to call .hidePopover();, because trying to hide an already-hidden popover generates a warning.

    I can easily achieve this with const isVisible = dialogRef.matches(':popover-open');, and it works like a charm, but testing environments still don't recognize this :popover-open pseudo class so I'm forced to check the display value instead:

    const isVisible = getComputedStyle(dialogRef).getPropertyValue('display') === 'block'

    I see that, as @annevk mentioned, this was already merged to the HTML side, but I can't find if there's been any update on this side

  15. mfreed7 commented on Apr 5, 2024

    @mfreed7
    ContributorAuthor
    1. I cannot intercept and cancel (event.preventDefault()) the toggle event to rely on the consumer of my component changing the open property; the most I can do is let the consumer know that the popover was closed.

    Right, this is by design, to avoid a slew of footguns. Sounds like you have a way to make your application work, which is good.

    1. Popovers can be light-dismissed by a user action (clicking outside or pressing the Escape key), so I need to first check if the popover is still visible before attempting to call .hidePopover();, because trying to hide an already-hidden popover generates a warning.

    You could also use the toggle event to keep track of the state, if you like.

    I can easily achieve this with const isVisible = dialogRef.matches(':popover-open');, and it works like a charm, but testing environments still don't recognize this :popover-open pseudo class

    It's landed in browsers, so I'm not sure why testing environments wouldn't have it.

    const isVisible = getComputedStyle(dialogRef).getPropertyValue('display') === 'block'

    Note that this is a bit brittle, since you can set display:whatever on the dialog manually.

    I see that, as @annevk mentioned, this was already merged to the HTML side, but I can't find if there's been any update on this side

    Right, this is part of the HTML spec, but doesn't appear yet in the selectors spec. @tabatkins @fantasai is it easy (and appropriate) to get :popover-open added to the CSS spec?

  16. changed the title [-][selectors-4] Introduce `:popover` pseudo class[/-] [+][selectors-4] Introduce `:popover-open` pseudo class[/+] on Apr 5, 2024
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

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions