Styling tooltips

Tooltips are pretty cool. Not only do they give you a preview of an entity before you decide to check it out, but in some cases they provide all the info you need without having to leave the current page (especially on Premium campaigns, where you can have them show a lot more than the basic 500-character excerpt from the entry). On the other hand, they can be a bit boring showing every preview in the same basic format.

When I created my DnD5 Condition Tooltips content pack and theme, one of my main goals was that you could hover a condition mention and see everything you need to know about it without moving from what you were reading. The second goal was to make it look like it came straight from the official rulebook, regardless of your campaign’s theming, and this guide will help you use the same tricks I did to customize tooltips to your own campaign or content pack.

HTML structure of a tooltip

Since Kanka’s tooltips are created via JavaScript when you hover a link and disappear from the page source when you move your pointer away, they can be very difficult to inspect and style in real time. To save you the trouble, you can copy the one below (current as of Kanka 2.0, November 2023) and paste it somewhere in your page inspector panel to see how your CSS changes affect it in real time. I will detail each component below.

<div data-tippy-root="" id="tippy-18" style="z-index: 9999; visibility: visible; position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate(353px, 387px);">
  <div class="tippy-box" data-state="visible" tabindex="-1" data-theme="kanka" data-animation="fade" style="max-width: 350px; transition-duration: 300ms;" role="tooltip" data-placement="bottom">
    <div class="tippy-content" data-state="visible" style="transition-duration: 300ms;">
      <div class="tooltip-content flex flex-col gap-2 p-1 kanka-tag-11 kanka-tag-ente">
        <div class="flex gap-2 items-center mb-1">
          <div class="flex-none">
            <div class="rounded-full w-10 h-10 cover-background" style="background-image: url('https://app.kanka.io/....png');"></div>
          </div>
          <div class="grow entity-names">
            <a href="https://app.kanka.io/w/1/entities/123" class="entity-name text-xl block"> Adam Morley </a>
            <span class="entity-subtitle text-base block">The Legend</span>
          </div>
        </div>
        <div class="tooltip-tags flex flex-wrap gap-2">
          <a href="https://app.kanka.io/w/1/entities/333" class="tooltip-tag" data-id="333" data-tag-slug="ente" title="Ente">
            <span class="badge color-tag rounded-sm px-2 py-1">Ente</span>
          </a>
        </div>
        <div class="tooltip-text text-sm">
          <p>The man, the legend, sir Adam Morley, born Adam Andlers, is a powerful noble! Important enough to have a link to his own entity. <span>Brave Sir Robin's Second, More Handsome Quest</span><br></p>
        </div>
      </div>
    </div>
    <div class="tippy-arrow" style="position: absolute; left: 0px; transform: translate(167px);"></div>
  </div>
</div>

Outer layers

The outermost layers (div[data-tippy-root] and div.tippy-box) mainly control the positioning and size of tooltips. You might want to do things like change the max-width on [data-tippy-root] and .tippy-box, or adjust the transition-duration on .tippy-box.

The next layer, div.tippy-content, also has a transition, as well as padding and font styles you may want to change:

.tippy-content {
	position: relative;
	padding: 5px 9px;
	z-index: 1;
}
.tippy-box .tippy-content {
	padding: .5rem;
	font-size: .8rem;
}

Inner layers

Things get more interesting with div.tooltip-content, as it has classes matching the tags on the target entity. That means you can style this part of the tooltip differently based on tags to use different colors, backgrounds, etc. to reflect an entity’s type or faction, for instance.

<div class="tooltip-content flex flex-col gap-2 p-1 kanka-tag-11 kanka-tag-ente">

As you can see, tags are identified both by ID and slug, similar to what is found on mentions as described in Introduction to campaign CSS - Mention attributes. It’s unfortunate that these classes aren’t on the outermost layer, but I will come back to this below with a workaround I used for DnD5 Condition Tooltips.

Coming back to .tooltip-content itself, it sets the padding and gap between the three blocks of content showing the entity’s info and excerpt as a flexbox:​

.p-1 {
	padding: .25rem;
}
.flex {
	display: flex;
}
.flex-col {
	flex-direction: column;
}
.gap-2 {
	gap: .5rem;
}

Its first child doesn’t have a very recognizable class but is a single-row flexbox for the entity’s image (if enabled in your campaign’s Interface settings), name and title (for characters). The entity’s image is set inline as a background-image on .cover-background, and it is subject to the following rules, which you can override to alter the sizing, position, etc.:

.cover-background {
	background-size: cover;
	background-repeat: no-repeat;
	background-position: 50% 50%;
}
.rounded-full {
	border-radius: 9999px;
}
.w-10 {
	width: 2.5rem;
}
.h-10 {
	height: 2.5rem;
}

.entity-names, .entity-name and .entity-subtitle don’t have anything too special on them other than display: block and font-related rules, and you can target them to change the font styling, center the text, etc.

.tooltip-content’s second child is .tooltip-tags, which simply shows the entity’s tags in a row. You may want to hide them entirely or change the size or margins of each .badge.

The third child and final layer is .tooltip-text, which contains the actual excerpt from the entity and is the class you would target for font styling that differs from the rest of the tooltip, or perhaps add an inner border or background. Note that depending on your campaign’s CSS, you may need to target paragraphs specifically with .tooltip-text p { ... } to override global rules, and likewise for other elements.

Here is a small part of DnD5 Condition Tooltips to wrap up with a practical example of targeting and changing a few of those elements:

/* Style the name */
.tooltip-content.kanka-tag-dd5condition .entity-name {
	font-family: "Andada Pro", serif;
	font-variant: small-caps;
	font-size: 15px
	...
}
/* Hide tags*/
.tooltip-content.kanka-tag-dd5condition .tooltip-tags {
	display: none;
}
/* Style paragraphs: mimic list items, which are not parsed in tooltips */
.tooltip-content.kanka-tag-dd5condition .dd5-condition p::before {
	content: "• ";
}

Tag-based styling on the outer layers

As we saw earlier, there are three layers of divs that we can’t target with tag-specific rules. For some styles like borders, backgrounds, padding or even tag-specific max widths, this is a problem. Thankfully, it’s solvable thanks to a fairly recent CSS pseudo-class: :has(). In short, it allows us to look at whether an element contains some other element, something that had long been missing from CSS (a selector could only really look at what was above or before it in the document tree, not after or below).

Since :has() is still recent, it is not fully supported on all browsers, especially on mobile. You can check for current compatibility stats and use fallbacks as explained below until it reaches sufficient adoption.

Unlike Kanka’s earlier tooltips, Tippy tooltips are nowhere near their associated link in the document object tree, which means we can’t target them based on their proximity to each other. Instead, we’ll have to rely on a subtle attribute of mentions called aria-expanded. By default, this attribute is set to false, but it switches to true when hovered:

<a href="https://app.kanka.io/w/1/entities/733" class="entity-mention" data-entity-tags="id-30 dd5condition" data-toggle="tooltip-ajax" data-id="733" data-url="https://app.kanka.io/w/1/entities/733/tooltip" data-loaded="1" aria-expanded="false">Grappled</a>

Since only one tooltip can be active at a time, and since the mention’s classes also indicate the target’s tags, as seen above, we can deduce the active tooltip’s tags even at the outermost level by checking if body contains a mention with the necessary tag(s) and aria-expanded="true":

/* Let’s target tooltips for mentions that target entities tagged with "dd5condition" */
 /* layer 1 */
 body:has(a.entity-mention[data-entity-tags~="dd5condition"][aria-expanded="true"]) [data-tippy-root] { ... }
 /* layer 2 */
 body:has(a.entity-mention[data-entity-tags~="dd5condition"][aria-expanded="true"]) .tippy-box { ... }
 /* layer 3 */
 body:has(a.entity-mention[data-entity-tags~="dd5condition"][aria-expanded="true"]) .tippy-content { ... }

Now, the problem with this is you may end up breaking things for people on browsers that don’t support :has(). In my DnD5 Conditions theme, I set a custom background image on the outer layers and text color on the inner content. Without :has(), a user will likely have trouble reading the text because the custom color and default background aren’t suited to each other. This can be solved with a feature query checking whether the browser supports it.

Below, I check if body:has(a.entity-mention) is considered valid by the browser. I don’t really need the full selector, as I’m only really concerned about :has(), so I shortened it to something I can recognize easily.

/* Fallback for :has */
@supports not (body:has(a.entity-mention)) {
	.tooltip-content.kanka-tag-dd5condition {
		background: url(https://kanka-user-assets...jpg), #dbcbae;
		padding: 5px 8px;
		border-radius: 3px;
	}
}

With the not keyword, I add a rule specifically for browsers that don’t support it; in this case, setting a slightly different background on .tooltip-content instead — the first layer where I can see the target entity’s tags. It doesn’t look as good as a full-sized one, but it comes close to the overall look I’m aiming for and maintains legibility for everyone.


And that’s about it! If this guide helped you, please consider supporting my work with a tip on my Ko-fi page =) I am also sometimes available for commissions to help directly with your plugins or CSS.

Last updated