JavaScript free tooltips in SASS / Bourbon / CSS

You don’t need JavaScript to have a good time.


Tooltips are ubiquitous. They’re everywhere on the net. Pretty much every front-end framework includes them. Whilst most are implemented leveraging javascript in some way, tooltips can be implemented easily and in a variety of ways with CSS alone.

This is certainly not the be-all-and-end-all of js-free tooltips. This method is a bit heavier on the mark-up. It’s just one way of doing it, there are more comprehensive / leaner methods out there like this one and this one using pseudo-elements, data-attributes and [attr] selectors. I just want to show how easy it is to put JavaScript / Bootstrap down for a minute and implement an entirely CSS version.

My only dependency is thoughtbot’s Bourbon mixin library (It’s brilliant btw.) for vendor prefixing transitions, transforms etc. At the time I wrote this there is no mixin for tooltips in Bourbon, but this may change in the future.

Plagiarism disclaimer!

This all borrows heavily from the Refills component library with a few modifications. Have a look anyway there’s tons of useful stuff there.

Advantages of this method

  • Allows internal formatting of the the tooltip and inclusion of a title, images, sub-elements movies, etc. that you can’t really do using css3 pseudo-elements and data-attributes (sans js that is).

“Movies!? That’s not a tooltip!” I hear you cry. Yeah whatever, don’t be such a prude.

Problems with this method

  • Size / positioning of the tooltip: It only ever appears below the tooltip text. Again, other ttip libraries mixins / allow for positioning, but this is just a quick demo. I’m sure you could have a play with the code and figure something out.
  • Nesting in the sass code: The nesting in the SASS is maybe a little bit too much for my liking, again you could refactor this to tidy it up.
  • Markup: The markup is a lot heavier than other css only tooltips that use data-attributes. But this does allow us to have greater control over the formatting and content of the tooltip.

Well then.

So this is what we’re aiming for:

http://www.lesmoffat.co.uk/assets/ttips.mp4

I used SASS for this. I love the clean easy to read syntax. It makes writing CSS a breeze. It also abstracts away the grunt work involved in creating more complex patterns and styles. So, let’s look at our markup first of all…

<span class=”tooltip-item”>
MEAN <!-- what is appearing in the paragraph -->
<span class=”tooltip ttip-trans”>
<h4 class=”ttip-title”>
Tooltip Title <!-- ttip title -->
</h4>
Lorem ipsum dolor sit amet <!-- ttip content -->
</span>
</span>

As you can see, we’ve got three elements nested inside each other.

span.tooltip-item

…is the container element, any unwrapped text in here will appear inline in the paragraph you include it in.

span.tooltip.ttip-trans 

…is the tooltip itself, the ttip-trans class is merely for transition animation

h4.ttip-title

…is the title of the tooltip. Obviously this can be any element other than a heading element.

Make sure that the main content of the tooltip comes after the title (duh!)

Getting Sassy

Like I mentioned, I used thoughtbot’s Bourbon library, you don’t need this, it just makes it handier for coding verbose vendor prefixes. But if you’re using this code verbatim, then you’ll need to download it.

So here’s the sass in it’s entirety.

$grey: #5e686d
$near-white: #eff6ec
.tooltip-item 
$base-border-radius: 0.2em
$base-line-height: 1.5em
$base-font-color: $near-white
$tooltip-background: $grey
$tooltip-color: $base-font-color
$tooltip-max-width: 20em
$tooltip-min-width: 20em
$tooltip-arrow-width: 8px
$tooltip-shadow: 0 2px 2px lighten($grey, 10%)
$tooltip-distance-from-item: 3.5em
$tooltip-arrow-distance-from-box: -0.5em
  border-bottom: 1px dotted $grey
display: inline-block
padding: 0
position: relative
text-align: center
cursor: help
  &:focus,
&:hover .tooltip
opacity: 1
+transform(scale(1))
visibility: visible
  .tooltip 
+position(absolute, $tooltip-distance-from-item null null null)
display: block
background-color: $tooltip-background
border-radius: $base-border-radius
box-shadow: $tooltip-shadow
color: $tooltip-color
font-size: 0.6em
line-height: 1.5em
margin: 0 auto
width: $tooltip-max-width
opacity: 0
+transform(scale(0))
+transform-origin(1.1em -1em)
padding: 1em
text-align: left
visibility: hidden
z-index: 10
  img, movie, object // if there's any media included in the ttip
width: 95%
height: auto
display: block
margin: 0.5em auto
  .ttip-title // formatting for the title
border-bottom: 1px dotted $tooltip-color
width: 100%
display: block
  &:before // the-caret-pointy-up-arrow
+position(absolute, null null null 0)
top: $tooltip-arrow-distance-from-box
color: $tooltip-background
content: ‘▲’
font-size: 2.5em
margin-left: 0
text-align: left
text-shadow: none
.ttip-trans // the transition bit
+transition(0.5s)
+transition-timing-function(cubic-bezier(.17,.01,0,1.5))

That’s quite a plateful. Let’s cut it up into some more bitsize morsels.

The first part is just your standard sass variable decalations, pretty self explanatory.

.tooltip-item  
$base-border-radius: 0.2em
$base-line-height: 1.5em
$base-font-color: $near-white
$tooltip-background: $grey
$tooltip-color: $base-font-color
$tooltip-max-width: 20em
$tooltip-min-width: 20em
$tooltip-arrow-width: 8px
$tooltip-shadow: 0 2px 2px lightgray
$tooltip-distance-from-item: 3.5em
$tooltip-arrow-distance-from-box: -0.5em

The next bit defines the appearance of the copy text associated with the tooltip.

  border-bottom: 1px dotted $grey // a little underline
display: inline-block // inline-block
padding: 0 // so it doesn't look weird
position: relative
text-align: center // so it's in the middle
cursor: help // for the fun of it

Most of it is merely chrome. The important part is making sure “position: relative” is declared to make sure that the absolutely positioned child element (see next block) has it’s position relative to it’s parent (tooltip-item) and not the document.

&:focus, 
&:hover .tooltip
opacity: 1
+transform(scale(1))
visibility: visible
.tooltip 
+position(absolute, $tooltip-distance-from-item null null null)
// bourbon mixin (position, top right bottom left)
display: block
background-color: $tooltip-background
border-radius: $base-border-radius
box-shadow: $tooltip-shadow
color: $tooltip-color
font-size: 0.6em
line-height: 1.5em
margin: 0 auto
width: $tooltip-max-width
opacity: 0
+transform(scale(0)) // transform mixin scale(0)
+transform-origin(1.1em -1em)
padding: 1em
text-align: left
visibility: hidden
z-index: 10

Again a lot of this is just chrome for display. However, notice “.tooltip” is abolsutely positioned in reference to it’s parent, that’s the main thing.

You’ll also have noticed that the element has been scaled to zero, essentaially hiding it, only scaling to it’s true size upon hover or focus.

Formatting the inner elements is pretty straightforward, nothing out of the ordinary.

img, movie, object // if there's any media included in the ttip
width: 95%
height: auto
display: block
margin: 0.5em auto
.ttip-title // formatting for the title
border-bottom: 1px dotted $tooltip-color
width: 100%
display: block

The caret is dealt with using the ::before pseudo-element, again positioned absolutely in reference to it’s parent, the “top” attribute declaring it’s distance from the the tooltip box.

&:before // the-caret-pointy-up-arrow
+position(absolute, null null null 0)
top: $tooltip-arrow-distance-from-box
color: $tooltip-background
content: ‘▲’ // yay for unicode!
font-size: 2.5em
margin-left: 0
text-align: left
text-shadow: none

The final declaration deals with the transition of the of the tooltip from invisible to invisible using a cubic-bezier easing ‘pop’ style function.

.ttip-trans // the transition bit
+transition(0.5s)
+transition-timing-function(cubic-bezier(.17,.01,0,1.5))

If you’re looking for an easy way to figure out cubic-bezier functions have a look at Lea Verou’s cubic-bezier.com.

So there you have it.

We’ve successfully coded some tooltips using only sass, no-js, no front-end frameworks.

Is it perfect?

No.

Can it be hacked easily enough?

Yes.

Should you go play with it some more?

Definitely.