Accessible Custom Semantic UI Tooltip
by Allen N.
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/container.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/grid.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/popup.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/popup.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/transition.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/components/transition.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.3/components/icon.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.3/components/list.min.css">
<div class="row">
<div class="ui grid container">
<div class="twelve wide column">
<h1>Heading 1
<tooltip data-title="What are Infectious Diseases?"
data-content="Infectious diseases are caused by pathogenic microorganisms, such as bacteria, viruses, parasites or fungi. They can be spread from one person to another, directly or indirectly, through fluid exchange or exposure to vectors, or from the environment.">
What's this?
</tooltip>
</h1>
<p>This is a test of H1</p>
<h2>Heading 2
<tooltip data-title="What are Infectious Diseases?">
Title only!
</tooltip>
</h2>
<p>This is a test of H2</p>
<h3>Heading 3
<tooltip data-content="Infectious diseases are caused by pathogenic microorganisms, such as bacteria, viruses, parasites or fungi. They can be spread from one person to another, directly or indirectly, through fluid exchange or exposure to vectors, or from the environment.">
Content...
SCSS
@function remSize($size) {
$rem_Size: $size / 16px;
@return #{$rem_Size}rem;
}
$dur: 420ms;
@mixin default-transition($timing: $dur) {
-webkit-transition: all $timing ease-out;
-moz-transition: all $timing ease-out;
-o-transition: all $timing ease-out;
transition: all $timing ease-out;
}
$color-purple: #693a77;
$color-white: #ffffff;
$color-blue: #0f80a7;
$color-black-light: #333333;
body {
overflow-x: hidden;
min-width: 320px;
background: #fff;
font-family: OpenSans,'Helvetica Neue',Arial,Helvetica,sans-serif;
font-size: 16px;
line-height: 1.4285em;
color: rgba(0,0,0,.87);
font-smoothing: antialiased;
}
tooltip {
display: inline-block;
position: relative;
height: remSize(24px);
padding: 0 0 0 remSize(8px) !important;
margin-right: remSize(8px);
line-height: remSize(24px);
font-size: remSize(14px);
font-weight: 600;
color: $color-purple;
vertical-align: middle;
cursor: pointer;
outline: none;
opacity: 0.6;
@include default-transition(150ms);
&:hover,
&:focus,
&.visible {
opacity: 1;
}
&:not(.no-icon) {
padding: 0 0 0 remSize(40px) !important;
}
.icon {
display: inline-block;
position: absolute;
top: 50%;
left: remSize(8px);
width: remSize(24px);
height: inherit;
vertical-align: middle;
border-radius: 50%;
background-color: $color-purple;
transform: translateY(-50%);
content: ' ';
pointer-events: none;
.navicon,
i {
position: relative;
width: remSize(24px) !important;
height: remSize(24px);
&:before {
position: absolute;
top: 50%;
left: 50%;
padding: remSize(5px);
transform: translate(-50%, -50%);
color: $color-white;
}
}
&.adjust-for-navicon {
background: none;
.navicon {
padding: 0;
font-size: 1.5rem;
&:before {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
}
}
.ui.popup {
z-index: auto;
& > .header {
...
JavaScript
// Make Tooltips more A11y compliant
$('tooltip').each (function() {
var toolTip = $(this),
remove_icon = false;
toolTip.attr('tabindex', '0');// make tooltip working with tabbing
if (toolTip.attr('data-icon') === 'none') {
remove_icon = true;
toolTip.addClass('no-icon');
} else {
toolTip.prepend('<div class="icon"></div>');
if (toolTip.attr('data-icon')) {
toolTip.children('.icon').prepend('<i class="fa fas fa-' + toolTip.attr('data-icon') + ' fontawesome-icon"></i>');
} else {
toolTip.children('.icon').prepend('<i class="fa fas fa-info fontawesome-icon"></i>');
}
}
// To make sure tooltips remain accessible if data-html is used, check that data-title and/or data-content is used
if (toolTip.attr('data-html')) {
if (toolTip.attr('data-title') || toolTip.attr('data-content')) {
// do nothing because all is well
} else {
alert("If data-html attribute is used for a tooltip, \ndata-title and/or data-content attributes are require for screen readers. \n\nMake sure these attributes clearly describe content within popup tooltip.");
toolTip.css("outline", "7px solid red");
}
}
if (toolTip.attr('data-title') && toolTip.attr('data-content')) {
toolTip.attr('aria-label', toolTip.attr('data-title') + ' ' + toolTip.attr('data-content'));
} else if (toolTip.attr('data-title')) {
toolTip.attr('aria-label', toolTip.attr('data-title'));
} else if (toolTip.attr('data-content')) {
toolTip.attr('aria-label', toolTip.attr('data-content'));
}
toolTip.popup({
on: 'focus',
position: 'bottom center',
boundary: $('html'),
lastResort: true
});
});
// If tooltip exists, add this bugfix for IOS devices
// because popups would not close after being opened.
if ($('tooltip').length) {
var once = false;
window.addEventListener('touchstart', function() {
var ios = isIOS();
if (ios) {
if (!once) {
once = true;
document.body.style.cursor =...