-
Notifications
You must be signed in to change notification settings - Fork 194
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
Add explainer for popover=hint
and hover triggering of popovers
#766
Changes from 7 commits
26a657c
5b59fa9
2232ac9
000f2ea
f10821c
6c3e65d
f191cec
9d12c06
50fe3eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
--- | ||
menu: Proposals | ||
name: Popover=hint and Hover Triggering (Explainer) | ||
path: /components/popover-hint.research.explainer | ||
pathToResearch: /components/popup.research | ||
layout: ../../layouts/ComponentLayout.astro | ||
--- | ||
|
||
# Popover Hover Triggering and `popover=hint` | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- [@mfreed7](https://github.com/mfreed7), [@scottaohara](https://github.com/scottaohara), [@aleventhal](https://github.com/aleventhal) | ||
- June 8, 2023 | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
{/* <!-- START doctoc generated TOC please keep comment here to allow auto update --> */} | ||
{/* <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> */} | ||
## Table of Contents | ||
|
||
- [Background](#background) | ||
- ["Hover" triggering](#hover-triggering) | ||
- [Add the `hover` value](#add-the-hover-value) | ||
- [CSS values to control delay](#css-values-to-control-delay) | ||
- [Example buttons](#example-buttons) | ||
- [Popover types](#popover-types) | ||
- [Example menu](#example-menu) | ||
- [`popover=hint` Behavior](#popoverhint-behavior) | ||
- [Accessibility](#accessibility) | ||
- [Why differentiate between rich and plain hints?](#why-differentiate-between-rich-and-plain-hints) | ||
- [A11y API support](#a11y-api-support) | ||
- [`::tooltip` pseudo element](#tooltip-pseudo-element) | ||
- [Monkeypatch spec](#monkeypatch-spec) | ||
- [Articles and References](#articles-and-references) | ||
|
||
{/* <!-- END doctoc generated TOC please keep comment here to allow auto update --> */} | ||
|
||
|
||
# Background | ||
|
||
The [Popover API](https://html.spec.whatwg.org/multipage/C#the-popover-attribute) ([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API), [OpenUI Explainer](https://open-ui.org/components/popover.research.explainer/)) is now part of the HTML standard. It allows developers to create popovers of varying types and also easily trigger them from buttons, without using Javascript. | ||
|
||
This explainer discusses two important extensions of the Popover API: | ||
|
||
1. The ability to trigger popovers via a "hover" type interaction. | ||
2. The ability to show a new type of popover for "hints". | ||
|
||
These two capabilities are orthogonal but closely related, and this explainer therefore discusses them both. | ||
|
||
The motivating use case, but certainly not the only use case, for these two capabilities is in the construction of "tooltips". Typically, tooltips are transient bits of additional information related to a bit of content. This additional information is *not required* and is supplementary in nature. Usually the user accesses this tooltip by hovering, keyboard-focusing, or long-pressing a control. | ||
|
||
# "Hover" triggering | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The existing popover API includes the concept of "invoking elements": | ||
|
||
```html | ||
<button popovertarget=foo popovertargetaction=show> | ||
Click me to show a popover | ||
</button> | ||
<div popover id=foo> I'm a popover </div> | ||
``` | ||
|
||
The `popovertarget` attribute can be used on buttons (and `<input type=button>`), and it points to the idref of an element with the `popover` attribute. In this configuration, clicking the button automatically shows the popover, without requiring Javascript. In addition, this linkage ensures that the accessibility tree is properly updated to represent the connection. The `popovertargetaction` attribute determines what action to take when the button is activated: `show`, `hide`, or `toggle`. | ||
|
||
## Add the `hover` value | ||
|
||
To support hover triggering, a new value is added: `popovertargetaction=hover`. When set to the `hover` value, the popover will be **shown** when the invoking element is **a)** hovered with the mouse, **b)** focused, or **c)** long-pressed (mobile). | ||
|
||
## CSS values to control delay | ||
|
||
The majority of hover-triggered UI has a set of delays associated with it. It tends to be jarring to the user if UI elements show up *immediately* upon hovering an element. For example, that causes elements to show and hide rapidly as the user moves their mouse across the screen. To allow the user to be more intentional, there is an expectation that the element of interest must be hovered for a short period of time before any popovers are triggered. Additionally, it is common for such hover-triggered popovers to also be hidden automatically when they are no longer hovered, again after a sufficient delay. This "hide delay" is particularly important in the case that there is a physical gap between the invoking element and the hint popover: the user must move the mouse from the invoker to the popover and cross that gap, and it is frustrating if the popover closes while that traversal is taking place. | ||
|
||
To facilitate these delays, two new CSS properties are added: | ||
|
||
- `popover-show-delay` - applied to the invoking element. The invoking element must be hovered, focused, or long-pressed for this long before the popover is shown. The default value for this property is `0.5s`. | ||
- `popover-hide-delay` - applied to the popover. The invoking element *or* the popover must be **not** hovered, focused, or long-pressed for this long before the popover is hidden. The default value for this property is `infinity`. | ||
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two properties feel useful for more than this. What intrinsic behaviours are needed for these? Another curiosity; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting feedback! I had envisioned these to work with popovers (and maybe dialogs?) only. How do you envision making them more general? As to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://codepen.io/keithamus/pen/yLQabJK this uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh nice - I see that you get that by adding Do you think this is worth opening a CSSWG issue to discuss? I mean about the "more general problem" question?
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
It is important to "debounce" these delays. Said another way, the delays are interpreted as *the time an element must be continuously hovered or de-hovered*. The naive Javascript implementation of this behavior is to just call `setTimeout(() => show/hidePopover(), delayValue)`. I.e. after the invoking element is hovered, wait `delayValue`, and then show the popover. But that does not properly handle the situation where the user hovers the element, and then de-hovers it, all within the delay time. In that case, no popover should be shown. Similarly, once the popover is shown and the user de-hovers the popover briefly but re-hoveres it again quickly, the popover should not close. | ||
|
||
## Example buttons | ||
|
||
Let's examine a small example: | ||
|
||
```html | ||
<button popovertarget=hint1 popovertargetaction=hover> | ||
Button 1 | ||
</button> | ||
<div popover=hint id="hint1">Hint 1</div> | ||
<button>Button 2</button> | ||
|
||
<style> | ||
button { popover-show-delay: 0.3s; } | ||
[popover] { popover-hide-delay: 0.6s; } | ||
</style> | ||
``` | ||
|
||
In this example, because of the `popover-show-delay: 0.3s`, hovering (with the mouse) or keyboard-focusing "Button 1", *and then waiting for 0.3 seconds* will cause "Hint 1" to be shown. It will stay showing as long as the button *or* the popover are hovered with the mouse or keyboard-focused. | ||
|
||
Because of the `popover-hide-delay: 0.6s`, the hint will be automatically hidden after 0.6 seconds, once "interest has left" the popover. That depends on how the popover was triggered: | ||
|
||
- If the invoker was **hovered** to trigger "Hint 1", then it will stay open as long as the invoker or the hint are still hovered. If the mouse if moved off of *both* the invoker and the hint for 0.6 seconds, the hint will be closed. | ||
- If the invoker was **keyboard focused** to trigger "Hint 1", then it will stay open as long as focus is contained within either the invoker or the hint. If keyboard focus leaves both invoker and hint for 0.6 seconds, the hint will be closed. | ||
|
||
## Popover types | ||
|
||
The new hover-triggering functionality should work for all `popover` types. There are use cases, e.g. for `popover=auto`; for example, a nested menu structure where sub-menus are shown when items in the primary menu are hovered or focused. | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Example menu | ||
|
||
```html | ||
<button popovertarget=menu1>Menu</button> | ||
<ul popover id=menu1> | ||
<li> | ||
<button popovertarget=submenu1 popovertargetaction=hover> | ||
Sub menu 1 | ||
</button> | ||
</li> | ||
<li> | ||
<button popovertarget=submenu2 popovertargetaction=hover> | ||
Sub menu 2 | ||
</button> | ||
</li> | ||
</ul> | ||
<ul class=submenu popover id=submenu1> | ||
Sub-menu #1 | ||
</div> | ||
<ul class=submenu popover id=submenu2> | ||
Sub-menu #2 | ||
</div> | ||
<style> | ||
button { popover-show-delay: 0.5s; } | ||
[popover].submenu { popover-hide-delay: 0.5s; } | ||
ul { list-style-type: none; } | ||
ul button { border: none; } | ||
</style> | ||
``` | ||
|
||
This example implements a popover menu (`id=menu1`) which has sub-menus (`id=submenu1` and `id=submenu2`) which can be triggered simply by hovering or focusing the main menu's buttons. | ||
|
||
# `popover=hint` Behavior | ||
|
||
When a hint is shown, it behaves differently from other popover types such as `popover=auto` and `popover=manual` in a few ways: | ||
|
||
| | `popover=auto` | `popover=hint` | `popover=manual` | | ||
| ------------- | ------------------------- | -------------- | -------------- | | ||
| Light dismiss | Yes | Yes | No | | ||
| Force-hides: | Other `auto`s and `hint`s | Other `hint`s | Nothing | | ||
| Nesting: | Yes | No | No | | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
First, hints should always be light dismissable. They are transient, supplementary information, so they should not require affirmative action to close them. Clicking or tapping outside or hitting the <kbd>ESC</kbd> key will close a hint. | ||
|
||
Second, when a hint is opened, it should not immediately close other `popover=auto` popovers that are open. For example, if a select picker UI is opened as a `popover=auto`, and the user hovers or focuses a unrelated element to see its hint, that action should not close the select picker. In essence, the hint is *even more transient* than the picker, so it shouldn't take precedence. However, if the user subsequently hovers/focuses a *second* unrelated element and another hint is shown, the first hint should be closed. It would be confusing to have two hints open at a time. So hints should close other hints. | ||
|
||
Finally, `popover=auto` popovers support "nesting": the idea that one popover is a descendant of another popover, for example in the case of nested menus. It makes sense in that example for the parent menu to stay open while the child menu opens. The same is not true for hints - it would be confusing for one hint to be nested inside another, and wouldn't make sense. Therefore, hints do not support nesting. | ||
|
||
To summarize the above, there can only be one open `popover=hint` on a page, many nested (but only 1 "root") `popover=auto`, and many `popover=manual`. | ||
|
||
# Accessibility | ||
|
||
A key distinction between types of hints is whether it may be important for a user to explore or interact with the hint. Consensus on these types of hints/tooltips can help with the resolution of [ARIA#979: Clarify the use of role=tooltip](https://github.com/w3c/aria/issues/979). | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- A plain hint contains only elements with a computed role of generic, text and images. | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- A rich hint contains other elements, such as links, form controls, lists or tables. These are hints that a screen reader user may need to interact with or explore. | ||
|
||
## Why differentiate between rich and plain hints? | ||
|
||
For a screen reader user, there already exists simple settings and commands to read accessible descriptions, which are just an additional piece of text associated with an object, such as used by a `title` or `aria-description` attribute. When a hint is just used for a prettier tooltip, a screen reader can reuse this simple mechanism and act like a title was present. There is no need for the user to navigate to it. | ||
|
||
However, when a hint is used for something more complex, such as a dialog that appears on hover, or a data table, then the user needs to be able to learn it’s there and navigate to it, so that they can use additional screen reader commands to interact with it. | ||
|
||
## A11y API support | ||
|
||
For the best screen reading experience, the implementation will need to expose different properties for a plain hint vs a rich hint (classification described above). | ||
|
||
- Plain hints: the browser will expose the flattened text string comprised of the hint's text and text alternative content via the invoking element. The actual popover target element and its descendants can be invisible/ignored in the AX tree. Note: this would be similar to how `title` attribute tooltips are treated today. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This list isn't formatted right in the preview. Maybe it would help to line up the "1.", "2.", etc. with the words "Plain" and "Rich"? I'm not sure if that will work -- it might require a few attempts to see what happens in the preview. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Markdown is cool, until it's not. I'll try. I think you're right about lining things up with the "Plain"/"Rich". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So @andrico1234 I'm leaving this as-is, since I tried several permutations and none seem to work. I think this might be the Astro MD interpreter or something? It works on my local MD viewer in several ways, but none of those work on the preview. |
||
1. If the invoking element lacks an accessible name, use the computed text of the hint as the element's accessible name. | ||
2. If the hint is not used as the accessible name, then the hint would be treated as the accessible description of the invoking element. A described by relation would point from the trigger to the hint element. | ||
|
||
- Rich hints: | ||
1. Add `aria-expanded` on the triggering element: the same treatment as for `popover=auto`. | ||
2. Minimum role of `tooltip` on the target — see [spec PR describing minimum role](https://github.com/w3c/html-aam/pull/454). | ||
3. Add `aria-details` pointing from the trigger to the popover. This allows screen readers to announce the presence of the popover and a command for navigating to it. | ||
mfreed7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
4. Problem: in general, the details relation does not work for hidden objects, and we can’t necessarily expect the hint to be complete before it becomes visible, as that may happen on-demand. So what happens if the user navigates to a hint popover’s trigger, and it’s not open. If they tabbed there, then we can make sure the popover is shown on focus. But, if they are navigating with a virtual buffer as is likely, how will they trigger the hint popover to open (the user would need to know to focus the trigger, or JAWS/NVDA would need to start focusing objects during navigation as other screen readers do), and then and how will the user know it’s there (should we use a new value of the haspopup object attribute for these, e.g. haspopup=hint)? | ||
|
||
# `::tooltip` pseudo element | ||
|
||
[CSSWG Issue 8930](https://github.com/w3c/csswg-drafts/issues/8930) discusses the addition of a `::tooltip` (or similarly named) pseudo element that could be used to generate tooltips via CSS, and style them. These are intended (though see the discussion) to be Plain Hints (as described above). This proposal may be complimentary to this explainer's feature proposals, or it might replace some of them. | ||
|
||
# Monkeypatch spec | ||
|
||
\{To-do: document the changes necessary to the [WHATWG/html spec](https://html.spec.whatwg.org/multipage/C#the-popover-attribute) for this proposal.\} | ||
|
||
|
||
# Articles and References | ||
|
||
- [MDN Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) | ||
- https://github.com/openui/open-ui/issues/530 | ||
- https://github.com/openui/open-ui/issues/526 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this
name
probably needs to be shorter, because it substantially widens the sidebar on https://open-ui.org/ and thus changes the layout a bit. (The current longest item is "Selectmenu Element (Explainer)".I'm not really sure what to suggest, though. I don't have any bright ideas other than "Popover Level 2 (Explainer)". I don't have a strong opinion about what the item in the document list should be, but the length of the current one makes it look bad.
(Alternatively, we could try to fix the CSS so that this will line-break, maybe even with hanging indent.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I went with
Popover Hint and Hover
for now. Not awesome, but at least it's short enough. Likely we should try to remove "(Explainer)" from all of them anyway, since it's quite repetitive and not informative.@andrico1234 for further comments. But I think ok for this PR.