styling your own tooltips

swap the browser's ugly grey box for something that actually matches your theme.

beginner css jquery
this is the tooltip i use across this whole site, the little lace-bordered box that pops up when you hover a link. we're going to build it from scratch, piece by piece, so you understand what every line does before you swap in your own border and colors.

what we're building

by default, when you add a title="..." attribute to an html element, the browser shows its own plain tooltip: small grey text, no styling, no control over font or position. we're going to replace that with a <div> that follows your cursor and looks however we want, using the same title attribute you're already used to.

we'll use a small jquery plugin called style-my-tooltips to do the actual "follow the mouse and swap the title text into a div" logic, and then style that div ourselves with css. the plugin does the boring javascript part; the css is where all the personality happens.

live example: hover over the links below. this is the exact tooltip css from the code snippet at the top of this tutorial, running for real on this page.

try hovering this link, or this one, or even a longer one that will wrap.

step 1 — load the plugin

add jquery and the style-my-tooltips plugin to your <head>, after jquery:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://static.tumblr.com/lspzyz3/xloqk6cgp/jquery.style-my-tooltips.js"></script>

this plugin watches every element with a title attribute. the moment you hover one, it hides the browser's native tooltip, builds a <div id="s-m-t-tooltip"> containing that title text, and moves it around to follow your mouse. that div is the only thing we need to style, and we never touch the html of the links themselves.

step 2 — call it on your links

somewhere after the plugin loads (usually right before </body>), tell it which elements should get the custom tooltip:

<script>
  $(function() {
    $("a[title], .tooltip[title]").style_my_tooltips({
      tip_follows_cursor: true,
      tip_delay_time: 30,
      tip_fade_speed: 300
    });
  });
</script>
  • $("a[title], .tooltip[title]"): the selector for which elements get a tooltip. by default this grabs every link that has a title attribute, plus anything with class tooltip, which is handy for spans or icons that aren't links
  • tip_follows_cursor: true: the tooltip tracks your mouse as it moves instead of staying pinned to one spot
  • tip_delay_time: 30: milliseconds to wait after hovering before the tooltip appears, in case you're just passing over it
  • tip_fade_speed: 300: how long the fade-in/fade-out animation takes, in milliseconds
tip: want a non-link element to have a tooltip too, like an icon or a piece of plain text? just give it class="tooltip" and a title attribute: <span class="tooltip" title="hey!">hover me</span>.

step 3 — style the tooltip box

once the plugin is running, hovering anything with a title will inject #s-m-t-tooltip into the page. that id is what we target with css, nothing fancier than styling any other div:

#s-m-t-tooltip {
  max-width: 150px;
  background-color: white;
  border: 7px solid;
  border-image: url(/media/borders/pink.png) 5 fill round;
  border-image-outset: 2px;
  outline: 1px dashed #e1b3c7;
  outline-offset: -6px;
  border-radius: 4px;
  font-family: tahoma;
  font-size: 7px;
  letter-spacing: 2px;
  color: #848484;
  position: fixed;
  z-index: 999999999 !important;
  pointer-events: none;
  padding: 4px;
  text-align: center;
  margin: 0;
}

let's go through it line by line:

  • max-width: 150px: keeps the tooltip from stretching all the way across the screen if the title text is long. anything longer just wraps onto a new line
  • background-color: white: the plugin doesn't add any background of its own, so without this the text would just float over whatever's behind it
  • border: 7px solid + border-image: this is the fun part: instead of a flat colored border, we're stretching a small png (a strip of lace, in my case) around the edge. 7px solid sets how thick the border area is, and border-image is what actually fills that area with the image instead of a solid color
  • border-image-outset: 2px: lets the border-image poke slightly outside the box's normal edge, so it doesn't feel cramped against the corners
  • outline: 1px dashed #e1b3c7 + outline-offset: -6px: a second, thinner dashed line sitting just inside the border-image, pulled inward by -6px. outlines don't affect layout the way borders do, so this is a cheap way to add a second decorative line without needing extra wrapper divs
  • font-family: tahoma + font-size: 7px + letter-spacing: 2px: tiny, spaced-out text is a big part of what makes it feel like this specific aesthetic. try swapping in your own site's font here
  • position: fixed: this is required, not optional. the plugin calculates cursor coordinates relative to the viewport, so the tooltip has to be positioned relative to the screen, not the page, or it'll drift out of place as soon as you scroll
  • z-index: 999999999 !important: makes sure the tooltip renders above literally everything else on the page, even things with their own high z-index. the !important is there because some themes set inline z-index styles the plugin can't otherwise beat
  • pointer-events: none: stops the tooltip div itself from intercepting mouse events. without this, the tooltip can flicker or get "stuck" as your cursor technically hovers the tooltip instead of the link underneath it

about that border-image: the live example above is using this exact lace png (blog.png) as its border. let's break down the shorthand:

border-image: url(/media/blog.png) 5 fill round;
  • url(/media/blog.png): the image to use. it's a single small square png with the lace pattern along its edges
  • 5: the "slice" value, in pixels from each edge of the source image. css cuts the png into a 3x3 grid using this number, then uses the 4 corner pieces as-is and stretches/repeats the 4 edge pieces around your border, leaving the center piece unused since we want to see through to background-color: white underneath
  • fill: tells the browser to also render that unused center piece, so the middle of the source image gets used as the tooltip's background instead of the css background-color. leave this keyword out if your source image doesn't have a background you want to reuse
  • round: how the edge pieces repeat to fill the length of each side. round squishes or stretches whole copies of the pattern to fit evenly, with no cut-off tiles at the corners, which keeps a repeating lace pattern looking clean. the alternative, stretch, just stretches one copy the whole way, which suits borders that aren't a repeating pattern

if you swap in your own image, the slice number should roughly match how many pixels of pattern sit along the edge of your png. too small and you'll barely see any pattern, too large and it'll look squished.

step 4 — tighten the text and move it off the cursor

two small tweaks make a big difference in how polished the tooltip feels. neither is in the plugin, they're both plain css additions to the same #s-m-t-tooltip rule:

#s-m-t-tooltip {
  ...
  line-height: 1.3;
  margin: 14px 0 0 16px;
}
  • line-height: 1.3: at a tiny font-size like 7-9px, the browser's default line-height leaves noticeably tall gaps between wrapped lines, it starts looking more like a spaced-out list than one line of text. a tighter line-height (somewhere around 1.2-1.4) keeps wrapped lines close together, which reads much better at small sizes
  • margin: 14px 0 0 16px: this is what pushes the tooltip out from under your cursor. the plugin positions the tooltip by setting inline top/left values that track the mouse, and for a position: fixed element, those values place the margin box, not the visible border box. so adding margin on top and on the left doesn't move where the plugin thinks the tooltip is, it just pushes the visible box away from that point, down and to the right of your cursor instead of directly underneath it
tip: the four margin values go top, right, bottom, left. increase the first number to push the tooltip further down, and the last number to push it further right. if your tooltip should appear above-left of the cursor instead, flip which sides get the margin.

step 5 (bonus) — matching right-click menu

the same aesthetic pairs nicely with a custom right-click context menu, so here's how to build one that shares the tooltip's color palette. start with the html: a hidden <ul> sitting anywhere on the page:

<ul id="context_menu">
  <li onclick="document.execCommand('copy')">copy</li>
  <li onclick="location.reload()">refresh</li>
</ul>

then the css:

#context_menu {
  background: #FEF8FC;
  border: 1px dotted #AAAAAA;
  letter-spacing: 1px;
  width: 70px;
  font-family: Verdana;
  font-size: 8px;
  position: absolute;
  display: none;
  z-index: 999999999;
}

#context_menu ul li {
  padding: 4px;
  z-index: 100000;
}

#context_menu ul li:hover {
  background: #FEF8FC;
  color: #848484;
  z-index: 100000;
}
  • display: none: the menu is hidden by default. a tiny script listening for contextmenu events is what flips it to display: block and moves it to the cursor's position
  • position: absolute: positioned relative to the page rather than the viewport, since (unlike the tooltip) it only needs to appear once, right where you clicked, not track the mouse continuously
  • note the selectors say #context_menu ul li: that only works if your <li>s are nested one level deeper, inside a <ul> that's inside #context_menu. since the markup above just uses #context_menu directly on the <ul>, you'd simplify those selectors to #context_menu li

the script to actually show/hide it on right-click:

<script>
  const menu = document.getElementById("context_menu");
  document.addEventListener("contextmenu", (e) => {
    e.preventDefault();
    menu.style.display = "block";
    menu.style.left = e.pageX + "px";
    menu.style.top = e.pageY + "px";
  });
  document.addEventListener("click", () => {
    menu.style.display = "none";
  });
</script>

live example: right-click inside this box.

right-click me!
  • copy
  • refresh

result

with just those two css blocks, you've replaced two of the browser's plainest built-in ui elements, the title tooltip and the right-click menu, with tiny styled pieces that actually match your site. once you understand that both are really "one div, positioned with js, styled with css," you can apply the exact same pattern to build custom cursors, custom scrollbars, or custom drag ghosts too.