Paul Paul - 18 days ago 7
PHP Question

How to create a pure CSS tooltip with HTML content for inline elements


TL;DR - See Adam's answer (accepted) or Update #5 in my
question for the current solution. My question shows my journey
getting to that solution in a rather long but explanatory way,
describing pitfalls and limitations.


I am creating a module that adds glossary term descriptions to the respective words used in a text. Everything works fine with plain text descriptions. For example, if there is a description for onomasticon (Another word for thesaurus), the following HTML

<p>An onomasticon is not a dinosaur.</p>


gets converted to



<p>An <dfn title="Another word for thesaurus">onomasticon</dfn> is not a dinosaur.</p>





Since the content (both article text and glossary term descriptions) come out of a CMS which allows the user to use HTML, the term's description may contain HTML, e.g.
Another word for <strong>thesaurus</strong>
. Using the above technique, this starts to get messy:



<p>An <dfn title="Another word for <strong>thesaurus</strong>">onomasticon</dfn> is not a dinosaur.</p>





This is by default since tag attributes may not contain tags themselves. This limitation can be bypassed by adding an additional element inside the
dfn
tag and adding some CSS to hide the element initially:



dfn span {
display: none;
}
dfn:hover span {
display: block;
position: absolute;
top: 2.2em;
background: #ccc;
}

<p>An <dfn><span>Another word for <strong>thesaurus</strong></span>onomasticon</dfn> is not a dinosaur.</p>





But now comes the part, I am failing to overcome. HTML tags like
<strong>
are inline elements which do not pose a problem. However, if the term's description contains a block element, this fails, e.g. if the description itself is contained in a
<p>
tag:



dfn span {
display: none;
}
dfn:hover span {
display: block;
position: absolute;
top: 2.2em;
background: #ccc;
}

<p>An <dfn><span><p>Another word for <strong>thesaurus</strong></p></span>onomasticon</dfn> is not a dinosaur.</p>





What happens here? Well, since block elements are not allowed inside most inline elements, the outer inline element is getting terminated and the inner block element is placed right after. Thus, the CSS selectors are not working anymore, plus you'll end up with extra line breaks. Of course, one might think of adding something like
dfn span p { display: inline-block; }
but this doesn't work either (neither does
inline
), it's invalid HTML5.

Although semantically incorrect, I tried to turn
<dfn>
into
<div class="dfn">
and the inside
<span>
to a
<div>
as well since dfn, abbr and cite elements may only contain phrasing context as well (just as span). This fails again due to
<div>
not being allowed inside
<p>
, again an unwanted line-break is added:



.dfn > div {
display: none;
}
.dfn:hover div {
display: inline-block;
position: absolute;
top: 2.2em;
background: #ccc;
}

<p>An <div class="dfn"><div><p>Another word for <strong>thesaurus</strong></p></div>onomasticon</div> is not a dinosaur.</p>





That has been the summary of my journey so far, trying to reach the holy grail of adding a tooltip - containing HTML - to an inline element - such as
<dfn>
,
<abbr>
or
<cite>
- using CSS only.

Before I now look into javascript-based solutions, my question is whether or not I missed an option which fulfils my requirements as listed below?


  • CSS only

  • tooltip must be added to
    <dfn>
    ,
    <abbr>
    or
    <cite>
    inside a paragraph

  • tooltip content may contain the following HTML tags: div, p, h2, img, figure, ul, ol, li



Update 1: Since I wrote the module (PHP), I am of course able to manipulate the term's description. However, I don't just want to strip all
<p>
tags but try to keep the intended markup.

Update 2: I found a rather crude solution. Let's say the description for
onomasticon
is some HTML snippet like:

<p>Another word for <strong>thesaurus</strong></p>
<p><img src="thesaurus.png" /></p>


My module will now convert all block elements to spans with appropriate class names, so the description becomes

<span class="p">Another word for <strong>thesaurus</strong></span>
<span class="p"><img src="thesaurus.png" /></span>


This way, I have inline elements which shouldn't break the phrasing context. Adding some more CSS to style the tooltip and make
span.p
behave somewhat like a
p
again, it all ends up to this.



dfn {
display: inline-block;
position: relative;
}
dfn > span {
display: none;
}
dfn:hover > span {
display: block;
position: absolute;
top: calc(1em + 5px);
max-width: 20em;
background: #ccc;
padding: .5em;
}
dfn img {
max-width: 12em;
}
dfn > span span.p {
display: block;
margin-bottom: .5em;
}

<p>An <dfn><span><span class="p">Another word for <strong>thesaurus</strong></span>
<span class="p"><img src="http://i.imgur.com/G0bl4k7.png" /></span></span></span>onomasticon</dfn> is not a dinosaur.</p>





This is my best looking option so far supporting limited HTML for inline element's tooltips. However, it's probably not the best option since it requires the hacky rewrite of HTML block elements.

Update 3: ChristianF's answer had a very important clue how to preserve HTML in glossary term definitions. If the description is not an element inside the dfn tag but a following sibling, we could still hide and show it, e.g. with the CSS selector
dfn + div.dfn-tooltip
. However, since most glossary terms will be found in a phrasing context, this will again break the layout, since divs are not permitted inside a paragraph.

So we would need an element, that can be both used in a phrasing context and contain block elements. According to this beautiful HTML structure diagram, the
<button>
tag would be the only suitable one.



p {
display: relative;
}
dfn + .dfn-tooltip {
display: none;
}
dfn:hover + .dfn-tooltip {
display: block;
position: absolute;
top: 3em;
max-width: 20em;
background: #ccc;
padding: .5em;
border: none;
}
.dfn-tooltip img {
max-width: 12em;
}

<p>An <dfn>onomasticon</dfn><button class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button> is not a dinosaur.</p>





This looks pretty promising. Of course, there are two major drawbacks: (1) a button element at this place is semantically incorrect and (2) the tooltip doesn't stay open when hovering itself since it's not a child of the dfn tag anymore.

Update 4: Taking one little step further to avoid the second drawback is to simply move the button element back into the dfn tag, since the context is the same.



dfn {
position: relative;
display: inline-block;
}
dfn > .dfn-tooltip {
display: none;
}
dfn:hover > .dfn-tooltip {
display: block;
position: absolute;
top: calc(1em + 5px);
max-width: 20em;
background: #ccc;
padding: .5em;
border: none;
}
.dfn-tooltip img {
max-width: 12em;
}

<p>An <dfn>onomasticon<button class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button></dfn> is not a dinosaur.</p>





Update 5 (final): Adam's answer brought up some nice technique to incorporate the title attribute's original intention, putting everything together, this is what I ended up with.



dfn {
position: relative;
display: inline-block;
cursor: help;
}
dfn:before {
content: attr(title);
}
dfn > .dfn-tooltip {
display: none;
}
dfn:hover > .dfn-tooltip {
display: block;
position: absolute;
top: calc(1em + 5px);
max-width: 20em;
background: #ccc;
padding: .5em;
border: none;
cursor: help;
}
.dfn-tooltip img {
max-width: 12em;
}

<p>An <dfn title="onomasticon"><button disabled class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button></dfn> is not a dinosaur.</p>





By the way, if anyone is interested, I am using this in a module called Onomasticon, which provides a basic glossary functionality for Drupal 8.

Answer

Note that W3C says :

Defining term: If the dfn element has a title attribute, then the exact value of that attribute is the term being defined.

Accordingly, you can have a simple solution where you put the term being defined inside the title attribute, and the definition inside

dfn::before {content: attr(title);padding: 0 0 1em;}      
dfn button {display: none; position: absolute}
dfn:hover button, dfn:focus button {display: block;}
<p>An
     <dfn title="onomasticon" tabindex="0"><button disabled>
       <p>Another word for <strong>thesaurus</strong></p>
       <p><img src="http://i.imgur.com/G0bl4k7.png" /></p>
      </button></dfn> is not a dinosaur.
</p>

I do not find any tag that can replace here the button element which seems to be the only one working here.

So we have to add the disabled attribute to the button element to disable its button behavior (focus) and set the tabindex on the dfn element to enable arrow navigation.

Comments