Description
Now that popover and anchor positioning are moving forwards, I wonder if it’s time to re-examine standardizing tooltip styling and adding a ::tooltip
pseudo-element so that authors can customize it.
History
- The earliest such proposal appears to be Tantek’s from April 2000. It received one neutral response and that was it.
- It was brought up again 2009. It got no opposition, some support, but very little discussion and no resolution.
- I brought it up again in 2012, which generated some more discussion, but the discussion went on tangents and eventually died.
- It was brought up again in 2017, which also didn't generate much discussion (just one message saying it doesn’t cover all use cases).
Since 6 years have passed since then, it seems like a good time to discuss it again. Maybe this time it will bear fruit. 😁
ETA: Apparently there was also a recent Open UI discussion on this with some good discussion.
Problem statement
Default UA tooltips are generally seen as inflexible, slow, and not aesthetically pleasing. Their styling is entirely disconnected from the element they describe (e.g. observe that the font size of the span does not affect the font size of the tooltip at all), and their color scheme is set by the OS, not the page (observe how in dark mode, the tooltip is dark, even when the page is actually not).
MacOS | Windows | MacOS dark mode |
---|---|---|
![]() |
![]() |
![]() |
For these reasons, nearly all popular websites employ some kind of scripting for custom tooltips. NPM packages for tooltips are in the millions of downloads per week. However, the styling employed by the vast majority of cases is actually pretty simple.
A few examples:
GitHub | Google Docs | ||||
---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Literally all component libraries include a component for tooltips (OpenUI research), however due to the limitations of components, they have to create an element for something that is conceptually not an element, but a behavior of another element. This results in unpredictable markup and complex selectors (e.g. consider <sl-tooltip>
; selectors now need to account for sl-tooltip
possibly being anywhere in the tree, consider how a selector like ul > li > ul > li
would need to be rewritten).
Older scripts depend on processing all [title]
elements on the page, renaming the attribute to data-title
or something, and adding event listeners (usually through event delegation). The downside of these scripts is that they don't work in shadow trees, and are often inaccessible.
And of course, there are always the authors that will roll their own, usually also resulting in poor accessibility.
Goals
- Custom tooltip styling (fonts, backgrounds, colors, shadows etc), including pointers
- Customization of show/hide animations (one of the major issues with the UA tooltips is how long they take to appear)
Non-goals
- Tooltips with arbitrary HTML content (these can be done with the popover API)
Proposal
We define a ::tooltip
pseudo-element and standardize the default styling through a UA rule, which could look like this:
::tooltip {
content: attr(title);
color: InfoText;
background: InfoBackground;
font-size: .8rem;
box-shadow: .1rem .1rem .2rem rgb(0 0 0 / .2);
transition: 0s 3s visibility;
@starting-style {
visibility: hidden;
}
}
Notes:
- To allow for pointers, this needs to allow sub-pseudo-elements.
- Do we need to restrict the properties that apply to this pseudo-element?
- Right now tooltip contents are set via the
content
property, which can be overridden to something else. Is this useful? I can see it being set to totally unrelated attributes, harming accessibility.
Issues
Positioning
As it currently stands, how the tooltip is positioned is still magic. This does make it easier to implement, but makes certain common styles very difficult: how to add a pointer when you don't know how the tooltip and originating element positions relate? We definitely don't want authors to have to deal with positioning tooltips manually, as that is insanely complicated. Perhaps we should expose some info to them about the relative positions of the tooltip and element that they can use in their styling? Maybe via env()
? Or, even better, it could be defined in terms of anchor positioning.
I think it's ok if we ship an MVP where pointers are not yet possible (which still covers a large number of use cases), but the design does need to allow for this to become possible in the future. Perhaps by restricting the properties allowed in ::tooltip
Customizing display triggers
As it currently stands, what makes the tooltip to be displayed is still magic. While this is fine, especially for an MVP, it would open up a ton of really nice use cases if this was grounded in specific user action pseudo-classes that generate the tooltip or not. Authors could then generate tooltips on :focus
, :focus-within
, via interactions on other elements (e.g. :focus + .foo::tooltip
), or even through entirely custom interactions (by toggling classes via JS).
Perhaps, if we agree that setting the content
property is what generates the box (like ::before
and ::after
), the default UA styles could look like this:
::tooltip {
color: TooltipText;
background: TooltipBackground;
font-size: .8rem;
box-shadow: .1rem .1rem .2rem rgb(0 0 0 / .2);
transition: 0s 3s visibility;
@starting-style {
visibility: hidden;
}
}
[title]:hover::tooltip {
content: attr(title);
}
The downside is that this couples the display trigger with the content. If someone wants to override the content, they also need to be up to date with the display triggers used and vice versa.
We could do something like this:
::tooltip {
content: attr(title);
color: TooltipText;
background: TooltipBackground;
font-size: .8rem;
box-shadow: .1rem .1rem .2rem rgb(0 0 0 / .2);
transition: 0s 3s visibility;
visibility: hidden;
}
[title]:hover::tooltip {
visibility: visible;
}
But this implies the box is pre-generated and simply shown, which I don't imagine is desirable for implementations.
::tooltip
in SVG
Instead of using a title
attribute, SVG supports a <title>
element, so when used in SVG, ::tooltip
should cover these tooltips as well. However, there is no way to specify something analogous to content: attr(title)
that works with that markup pattern, which may be another reason to axe that and have the content be magic (prefixes and suffixes can always be added via ::before
and ::after
). Or alternatively, we can define a new keyword or function for content
that returns the tooltip content, regardless of where it comes from.
Not exactly within the purview of the CSS WG, but it would be nice if in the future we could backport this element into HTML, to cater to the use cases that require more rich tooltip content without having to deal with all the plumbing and positioning manually. I do wonder what web compat would be like — I suspect there must be some clumsy author code out there that assumes there is only a single <title>
element on the page, and it contains the document title. Though inline SVG would break those already.