Interest Invokers (Explainer)
- Mason Freed, Keith Cirkel
- Last updated: January 22, 2025
Table of Contents
- Table of Contents
- The Pitch in Code
- Introduction
- HIDs and Interest
- Mouse delays
- The
:has-interest
Pseudo Class - Implementation Details
- Example Code
- Mockups
- Accessibility
- FAQ (Frequently Asked Questions)
- Why No
interestaction
Attribute? - Why a keyboard hot-key and not just keyboard focus?
- Why the name
interest
? Why nothover
orfocus
? - Why is
interesttarget
supported on more elements thancommandfor
? - Why is
interesttarget
not unlimited, liketitle
is? - Safe Area Triangle
- What if I (the developer) don’t want the touchscreen context menu
- Why No
The Pitch in Code
<a interesttarget="my-hovercard" href="...">Hover to show the hovercard</a>
<span popover=hint id="my-hovercard">This is the hovercard</span>
Introduction
The Invoker Commands API, consisting of the command
and commandfor
attributes, provides an easy declarative way to make buttons directly trigger actions on a target element, such as showing a popover or a modal dialog when the button is clicked. This API, interesttarget
, is very similar, in that it also provides a declarative way to invoke actions on a target element. However, rather than being activated via a click on the element, this API uses a lighter-touch way for the user to “show interest” in an element without fully activating it. For example, a link <a href="..." interesttarget=foo>
element can be hovered with the mouse, which might trigger a hovercard preview of the link, without actually navigating to the link destination URL. Of course, if the link is clicked, then a normal navigation will occur.
The interesttarget
capability is supported on these interactive elements:
<button>
<a href=something>
- SVG
<a href=something>
<area>
.
It is possible that this list could be expanded later: see #908 and #1138. It is also possible that some elements could be removed from the list: see #1138.
The user will be able to “show interest” in an element through various means. For mouse users, hovering the element for a period of time will “show interest”. For keyboard users, the UA will provide a hot-key such as Alt-Down, which will “show interest” in the element. See the HIDs and interest section for all the details.
Since one of the primary use cases for this API is the activation of hovercards, when the target element of interesttarget
is a popover (with the popover
attribute), the popover will automatically be shown. This provides an easy way to allow disclosure of high fidelity hovercards in a more accessible and declarative way. It is also possible to connect other actions to the target element, via the "interest"
event: when interest is shown in an element with interesttarget
, an InterestEvent
with type "interest"
will be fired at the element referenced by interesttarget
.
It is also possible to trigger actions that occur when the user “loses interest” in the element and its target. In a very similar way to how “showing interest” is defined, “losing interest” happens in different ways based on the input modality. But the concept is clear: once the user has shown interest in an element, they need a way to show that they have “lost” that interest. For the typical hovercard use case, losing interest would cause the hovercard/popover to close. In all cases, when interest is lost, an InterestEvent
with type "loseinterest"
will be fired at the target element.
HIDs and Interest
Users access the web using many different Human Interface Devices (HIDs), including mouse, keyboard, touchscreen, and even things like voice control, eye tracking, and other specialty HIDs. However, as mentioned above, “showing” and “losing” interest are intentional abstractions that do not refer to specific actions such as “hover” or “long press”. This is on purpose: the developer should not have to worry about handling all of the various input modalities specifically, in the same way that a <button>
element can be activated in various ways such as tapping, clicking, or hitting Enter on the keyboard. The user agent provides this functionality “for free”: developers don’t need think about HIDs. This provides two benefits:
- The implementation burden is lowered, since developers don’t need to implement support for all forms of HID.
- Users, particularly those using less common HIDs, are benefited, since all users have a way to show interest in elements. They don’t get locked out of particular site functionality simply because the site developer didn’t make specific affordances for their particular HID.
Mouse
Mouse users will show interest in an element by mouse-hovering the element for a period of time. They will lose interest in that element when they de-hover the element (i.e. hover something unrelated) for a period of time. (See the Mouse delays section for more detail.) If the target is a popover, then both the element with interesttarget
and the target popover need to be de-hovered. In this way, a user can hover to get a hovercard, and then move the mouse over to that hovercard (e.g. to select/copy some text) without the popover closing.
Keyboard
For keyboard users, the UA will provide a hot-key such as Alt-Down, which will “show interest” in the element. Another hot-key such as the ESC key will provide a way to trigger “loseinterest”. These hot-keys must be chosen by the UA to be convenient for the user, while also not conflicting with existing UA-provided and OS-provided hot-keys. Other ideas include Alt-Space or Ctrl-Space for the show interest hot key.
Additionally, the appearance of the focus ring will be altered slightly when focus is on elements with interesttarget
. This allows the user to discover that something is “different” about this element, and know that the keyboard activation hot-key will allow them to show interest. Since accessibility guidelines state that color alone cannot be enough to differentiate states, this focus ring change will likely need to be something like a slightly-thicker outline, or other shape change.
An alternative idea is to instead change the “location” hint that most browsers include in the lower-left corner when focusing a link element. This area currently shows the destination URL for the link, but it could have additional text added such as “press alt-down for additional information” when an element with interesttarget
is focused. This could also be shown on the bottom-right side of the screen, to avoid interfering with the URL hint.
See https://github.com/openui/open-ui/issues/1133 for a much more detailed conversation with developers about keyboard behavior. That extended discussion led to the behaviors described in this section.
Touchscreen
For touchscreen users, the widely-adopted standard for “showing interest” in an element is the long-press gesture. For native apps, this is the user-expected shortcut to show context menus and access other actions without explicitly activating the element. (See this comment for more context on this point, from developers.)
On the web, however, most browsers already overload the long-press gesture to provide additional functionality. For example, long-pressing on plain text nodes often creates a text selection around that text, and sometimes provides an additional context menu containing actions like “copy” or “look up”. Long-pressing a link <a>
element in most browsers provides a more involved context menu with additional operations such as “open in a new tab”, “add to reading list”, or “share”. Users often appreciate these additional capabilities, and do not want to lose access to them. Therefore, this proposal provides a way to keep both capabilities: the interesttarget
behavior and the existing context menus and behaviors.
To show interest in an element via touchscreen, the user simply long-presses that element. This does not activate (e.g. click) the element, it merely triggers interest in the element. This long press immediately fires the "interest"
event, but also shows any context menus that would have shown if the interesttarget
attribute were not present. So both things happen together. In the case of a popover target element, the idea is that the hovercard popover shows up in addition to the context menu, so that the user can tap on either of those things. The user can trigger "loseinterest"
by tapping either outside the target popover, or on one of the provided context menu items (if any). If the user taps outside both the context menu and the popover, that should constitute both a loss of interest (closing the popover) and also a signal to close the context menu (since on most platforms, tapping outside the context menu closes it).
One difficulty of the above proposal is that the two things (the popover target and the UA-provided context menu) need to coordinate, at least on positioning. It would be a bad user experience if the context menu showed up on top of the popover, obscuring part or all of it. One option would be to make sure the UA context menu is movable, so it can be moved out of the way by the user, if it obscures something important. But in discussions with developers (see, e.g., this comment), it is clear that this API simply cannot make it harder to access the context menu. So more coordination is needed. To provide this coordination, a few more things will be included:
- When the long-pressed element has
interesttarget
, any UA-provided context menu will not blur or otherwise obscure the web content it covers. In this way, the page will still be visible “under” the context menu. - The portion of the renderer window that is still visible (not obscured by the context menu) will be exposed to the developer via four new
env()
values, tentatively calledcontext-menu-inset-top
,context-menu-inset-right
,context-menu-inset-bottom
, andcontext-menu-inset-left
. In this way, the developer can safely position the target popover within the area that isn’t obscured by the context menu. (See the mockups section.) - An attempt will be made to provide at least half of the screen (or a “reasonable” amount of screen real estate) for developer content, e.g. by moving the context menu down rather than centering it within the viewport.
See https://github.com/openui/open-ui/issues/1052 for a much more detailed conversation with developers about touch screen behavior. That extended discussion led to the behaviors described in this section.
Other
The “other” category includes many HID types, including assistive technologies, eye tracking and VR interfaces, voice control systems, switch access, and future HIDs that haven’t been developed yet.
Since this category of HIDs contains less-commonly-used HID types, and because it also contains future HIDs that don’t even exist yet, the specification for interesttarget
will simply mandate that the User Agent provide a way for users to both “show interest” and “lose interest” in elements, no matter what HID is in use, without specifying exactly what form that might take. In some cases, this behavior likely cannot be specified, and must be left to the discretion of the user agent. For example, an explicit hand gesture might be needed for an eye tracking VR interface. In the to-be-invented case, it stands to reason that devices might introduce new and novel concepts of showing/losing interest.
There is another category of devices which also falls into the “Other” category: very small touchscreens. One example here is a watch face with a small touchscreen that can render web content. In this case, as with the other devices in this category, the UA must provide a usable way to show interest in an element.
Mouse delays
When the HID is a mouse, where hover/de-hover is the method for showing and losing interest, delays are important for a number of reasons:
- Simply hovering an element should not be enough to show interest in that element, since the user might just be moving the mouse from one place to another on the screen. It would be highly distracting to the user if such a move caused many popovers to show up. For this reason, showing interest needs to be done by hovering an element for a period of time.
- For a similar reason, losing interest cannot be provided by just de-hovering the element. For example, in the common case of a link that has a hovercard, it is very common for the hovercard to be separated from the element by some padding space. And it is also common for the user to want to move their mouse from the link to the hovercard, e.g. to select and copy some text. However, since there’s a gap between the elements, moving the mouse across that gap constitutes a de-hover of both the element and its target. For this reason, losing interest needs to be done by de-hovering the element (and its target) for a period of time.
Both of these time periods need to be CSS-configurable, because different use cases can require different delays. For example, a responsive gaming site might want very short delays, including potentially zero-delay. On the other hand, information-rich sites such as Wikipedia might want longer delays to ensure their content is easily accessible to all. To achieve this, two properties, interest-target-show-delay
and interest-target-hide-delay
, will control the corresponding delays. Because, as described above, both of these delays are fairly critical to proper operation of the interest mechanism, both properties will default to a non-zero value, 0.5 seconds. A shorthand interest-target-delay
property will control both.
There was some discussion about the need to allow UAs to modify these delays based on user needs. For example, some users might require longer delays to be able to navigate between popovers. There was a discussion about potentially using keywords (e.g. “short”, “medium”, “long”) rather than explicit numbers for these properties. However, there was strong pushback against that for several reasons, the primary one being that it’s not possible to normalize all sites down to a small set of named values. The alternative suggestion, which meets the use case, is that the UA is allowed to modify delays, as needed, to be a proper agent for the user. One example might be a setting like “multiply all delays by 10x”.
See https://github.com/w3c/csswg-drafts/issues/9236 for a much more detailed conversation about these hover delay settings. That extended discussion led to the behaviors described in this section. Notably, the OP and initial comments started in a somewhat different place than the eventual conclusion, which is what is described here.
The :has-interest
Pseudo Class
It is handy to be able to select elements that both a) have the interesttarget
attribute, and b) are currently being shown interest. The :has-interest
pseudo class matches elements in exactly this state. Two common use cases arise:
- Styling the trigger element to indicate that it’s showing interest:
:has-interest {
background-color: lightgreen;
border: 2px solid green;
}
- “Speeding up” the
interest-target-show-delay
when another element already has interest. This is a common request: for example, the first popover hovercard takes ~1 second to show up. But then if you quickly hover another element that also triggers a hovercard, that one shows up much more quickly:
[interesttarget] {
interest-target-delay: 1s;
}
container:has(:has-interest) [interesttarget] {
interest-target-show-delay: 0s;
}
Implementation Details
In the style of commandfor
, we propose to add a global attribute called interesttarget
, which can be used on <button>
, <a>
, and <area>
elements:
interface mixin InterestInvokerElement {
[CEReactions] attribute Element? interestTargetElement;
};
HTMLButtonElement includes InterestInvokerElement;
HTMLAnchorElement includes InterestInvokerElement;
HTMLAreaElement includes InterestInvokerElement;
SVGAElement includes InterestInvokerElement;
The interesttarget
value should be an IDREF pointing to an element within a single document or shadow root. The interestTargetElement
IDL attribute also exists on the element to imperatively assign a node to be the invoker target, allowing for cross-root invokers (in some cases, see attr-asociated element steps for more). We define this target element to be the “Interestee”.
Elements with an interesttarget
attribute will dispatch an InterestEvent
on the Interestee when the element Shows Interest or Loses Interest. When the user Shows Interest in the element, the event type will be "interest"
. If the user has already shown interest in the element, and interest is subsequently lost, an InterestEvent
with the type of "loseinterest"
will be dispatched on the Interestee. The event also contains a source
property that will reference the element with interesttarget
. InterestEvent
objects are always non-bubbling, composed, cancellable events.
[Exposed=Window]
interface InterestEvent : Event {
constructor(DOMString type, optional InterestEventInit interestEventInit = {});
readonly attribute Element source;
};
dictionary InterestEventInit : EventInit {
Element? source = null;
};
Both interesttarget
and commandfor
can exist on the same element at the same time, and both should be respected/functional, since they are activated using strictly separate actions by the user.
While commandfor
is ignored for <button>
s that are form participants, or have type=submit
, the interesttarget
is still valid in these scenarios.
Example Code
Popovers
Use interesttarget
to declaratively build a hovercard using popover
(in this case using popover=hint
):
<button interesttarget="my-popover" type=button>Hover for popover</button>
<div id="my-popover" popover="hint">Hello world</div>
Custom delays and multiple actions
Expanding on the example above, here we add custom hover delays for show and hide, and also show that a single element can have both interesttarget
and commandfor
:
<button interesttarget=hovercard commandfor=application command="show-modal" type=button>Start</button>
<div id=hovercard popover=hint>Clicking this button will open a new application</div>
<dialog id=application>Modal application window</dialog>
<style>
[interesttarget] {
interest-target-show-delay: 1.2s;
interest-target-hide-delay: 800ms;
}
Custom behaviour
The InterestEvent
interface allows for custom JavaScript to be triggered when interest is shown and lost, without having to wire up manual event handlers (such as for mouseenter
and mouseleave
):
<button interesttarget="my-custom" type=button>
While interest is being shown in this button, the div below will be displayed.
</button>
<div id="my-custom">Supplementary information</div>
<script>
const custom = document.getElementById("my-custom");
custom.addEventListener("interest", (e) => {
custom.classList.add('active')
});
custom.addEventListener("loseinterest", (e) => {
custom.classList.remove('active')
});
</script>
Feature detection
To feature-detect this overall API, the following code does the trick:
const supported = HTMLButtonElement.prototype.hasOwnProperty("interestTargetElement");
Mockups
Touchscreen hovercard
For touchscreens, the the Touchscreen section describes how the provided env()
variables can be used to position the popover to an unobscured position on the screen. This diagram shows the rectangle described by the context-menu-inset-*
environment variables:
Using those insets, a developer could implement a long-press activated hovercard, to achieve something like this:
Accessibility
Since popover=hint
is a common target for interesttarget
, for the purpose of building “tooltips” and “hovercards”, there are a few additional definitions and considerations:
- When the target is a
popover=hint
element, and that hint popover contains only elements with a computed role of generic, text or image, we will call that a “plain hint”. - When the target is a
popover=auto
orpopover=manual
, or it is apopover=hint
with contents beyond those allowed by “plain hint”, then we will call that a “rich hint”.
Plain hints (aka tooltips)
For a screen reader user, there already exist 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 plain 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 the hint popover itself, since there’s nothing to explore.
For the above reason, the browser will simply expose the the contents of a plain hint on the interest invoker element. The actual popover element and its descendants can be invisible/ignored in the AX tree. In particular:
- If no other accessible name is available, use the
popover=hint
’s inner text for the accessible name. - If it’s used as the name, then use it to compute the description, setting the
describedby
relation to point from the interest invoker to the hint element - There is no need to make
aria-details
connections or setaria-expanded
on the interest invoker for plain hints.
Rich hints (aka hovercards or other)
Since rich hints have additional content that might want to be explored directly, additional connections need to be made here:
- Set a minimum role of “tooltip” on the target popover.
- Set
aria-expanded=true
when the rich hint is open, andfalse
when it is not. - Create an
aria-details
relation between the interest invoker and the rich hint popover.
As with popovertarget
, when a rich hint is shown, sequential focus navigation will be adjusted so that the target popover is “next” in the tab navigation cycle after the interest invoker. Since by definition “plain hints” do not have anything focusable, sequential focus navigation is not affected.
See also, https://github.com/w3c/aria/issues/979.
FAQ (Frequently Asked Questions)
Why No interestaction
Attribute?
The command
/commandfor
API, as its name suggests, provides two attributes. One (commandfor
) is the link to the target element, and the other (command
) describes what action to take on the target element. That is appropriate, because when activating a button, many different types of actions could be desired, even for the same target element. A button might want to open, open-modal, close, or request-close a dialog, for example. Or it might want to play, pause, or mute a video.
The same is not true for interesttarget
. When the target is a popover, the only set of actions that makes sense is to show the popover when interest is shown, and hide the popover when interest is lost. There are no identified use cases that invert those actions. Alternatively, defining show and lose interest actions to both toggle the popover can lead to serious out-of-sync problems that would be major footguns. So for popovers, there is no need for a separate attribute controlling the behavior, because there is only one rational set of behaviors. And as described above, popovers are the only kind of target element that comes with default actions. Other actions might use the "interest"
and "loseinterest"
events to add additional behavior, but in that case, they can provide their own definitions of actions to take. And again because of the above points, there isn’t a future-compat problem like there might be for the commandfor
attribute.
See https://github.com/openui/open-ui/issues/1064#issuecomment-2581511411 (particularly this comment) for more discussion of this section.
Why a keyboard hot-key and not just keyboard focus?
There are pros and cons to both methods.
Focus element to show interest (possibly after delay)
There are two approaches: instantly show interest when an element is focused, or show interest after the element has been focused for a period of time. Most people seem to favor a delay, to avoid extreme annoyance for keyboard users. Perhaps re-use the mouse delays or create a separate version for keyboard focus delays. To avoid accidental neglect of the keyboard delays/users, it might be better to re-use the same delays for both mouse and keyboard.
Pros:
- very discoverable - simply focus an element and wait.
Cons:
- hard to avoid / annoying. Users simply trying to keyboard-navigate through a document might inadvertently trigger popovers. Note that if the target popover is a “rich hint”, it will be next in the tab navigation cycle, forcing the user on a “detour” in this case. It’s further possible that the rich hint itself has additional interest targets, which can open their own rich hints, trapping the user for a significant number of tab stops.
- can be slow to activate, due to the delay.
Hot-key to show interest
Pros:
- No chance for “hijacking” the tab navigation cycle.
- Fast/immediate to activate (no delays needed).
Cons:
- Not very discoverable.
Conclusion: hot-key
Mouse users are fundamentally different from keyboard users, in that a mouse user can roughly-instantly move the mouse between any two arbitrary points on the screen. In contrast, keyboard users live within a sequential “linked list” of focusable elements, and can only navigate forward or backward, one step at a time. For this reason, keyboard users will have a harder time avoiding accidentally showing interest in elements that they merely stopped on briefly while navigating to another element. The same can be true for mouse users, if they stop their mouse while moving between points, but this is much more likely to happen via keyboard focus navigation.
Hot-keys of all types are often not very discoverable. “Power users” of the keyboard tend to search out these hot-keys and remember them, while “occasional” keyboard users do not. Here, we rely on the fact that most UX designers consider tooltips and hovercards to be “auxiliary” and not required for proper functioning of the site.
The idea (described in the keyboard section) to add helpful text to the “location” area on the bottom left of the browser window may alleviate much of the lack of discoverability here.
See https://github.com/openui/open-ui/issues/1133.
Why the name interest
? Why not hover
or focus
?
Much like click
, hover
or focus
are specific to certain types of HID, and are not terms which encompass all viable methods of interaction. Many alternatives were discussed and it was deemed that interest
is the best name to explain the concept of a “hover or focus or equivalent”.
See https://github.com/openui/open-ui/issues/1136.
Why is interesttarget
supported on more elements than commandfor
?
While invocation should only be limited to buttons, disclosure of supplementary information can be expanded to all interactive elements. There are many useful use cases for offering a hovercard on anchors, such as signalling that they are external, or that they will open in a new window, or to show preview information (think: preview windows on iOS Safari or the hovercards that display on GitHub over a user’s handle). To start, a limited set of elements is supported, to make sure they can be made accessibly. This set could be expanded in the future.
See https://github.com/openui/open-ui/issues/839.
Why is interesttarget
not unlimited, like title
is?
It could be considered a mistake to allow title
on all elements; as adding interactivity to non-interactive elements creates many problems. Limiting where interesttarget
is allowed aims to create a “pit of success”, guiding developers to use it only on interactive elements, where it makes sense.
For example, arbitrary elements are not usually focusable, and it is unclear how to ensure that keyboard users can still show interest in these elements. It is not impossible, however: perhaps elements with interesttarget
become focusable by default, or only work if tabindex
has been used. However, these potentialy solutions are not particularly popular, especially among accessibility experts and keyboard-users. So more work would be necessary to navigate the pitfalls.
Safe Area Triangle
The events interest
and loseinterest
are intentionally abstract to allow more complex usability concepts to unfold. It is possible that a future capability might be to add automatic “safe areas” or “hit triangles”, which allow the user to move the pointer between the Interest Invoker (e.g. the button) and Interestee (e.g. the hovercard), regardless of the interest-target-hide-delay
setting. See #963 for more.
What if I (the developer) don’t want the touchscreen context menu
It might be the case that in some circumstances, the “developer knows best” that the UA-provided context menu isn’t needed/helpful on some element that uses interesttarget
attribute. In that case, the (existing) mechanism to do that is to call preventDefault()
on the contextmenu
event, which will cancel the UA menu.