diff --git a/lib/visual.inc.php b/lib/visual.inc.php
index 86efbfb31e576908dc46678b48aa9992155ed0c7..9dca1d189c8d27fa6c4d4f01020905fd62dcaeb2 100644
--- a/lib/visual.inc.php
+++ b/lib/visual.inc.php
@@ -596,7 +596,12 @@ function tooltipIcon($text, $important = false, $html = false): string
 
     // render tooltip
     $template = $GLOBALS['template_factory']->open('shared/tooltip');
-    return $template->render(compact('text', 'important', 'html'));
+    return $template->render([
+        'text'       => $text,
+        'important'  => $important,
+        'html'       => $html,
+        'tooltip_id' => md5($text)
+    ]);
 }
 
 /**
@@ -608,9 +613,13 @@ function tooltipIcon($text, $important = false, $html = false): string
 function tooltipHtmlIcon($text, $important = false)
 {
     // render tooltip
-    $html = true;
     $template = $GLOBALS['template_factory']->open('shared/tooltip');
-    return $template->render(compact('text', 'important', 'html'));
+    return $template->render([
+        'text'       => $text,
+        'important'  => $important,
+        'html'       => true,
+        'tooltip_id' => md5($text)
+    ]);
 }
 
 /**
diff --git a/resources/assets/javascripts/bootstrap/tooltip.js b/resources/assets/javascripts/bootstrap/tooltip.js
deleted file mode 100644
index c84042b82534486b0700efea51f1fad77c4396bb..0000000000000000000000000000000000000000
--- a/resources/assets/javascripts/bootstrap/tooltip.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// Attach global hover handler for tooltips.
-// Applies to all elements having a "data-tooltip" attribute.
-// Tooltip may be provided in the data-attribute itself or by
-// defining a title attribute. The latter is prefered due to
-// the obvious accessibility issues.
-
-var timeout = null;
-
-STUDIP.Tooltip.threshold = 6;
-
-$(document).on('mouseenter mouseleave focusin focusout', '[data-tooltip],.tooltip:has(.tooltip-content)', function(event) {
-    let data = $(this).data();
-
-    const visible = event.type === 'mouseenter' || event.type === 'focusin';
-    const offset = $(this).offset();
-    const x = offset.left + $(this).outerWidth(true) / 2;
-    const y = offset.top;
-    const delay = data.tooltipDelay ?? 300;
-
-    let content;
-    let tooltip;
-
-    if (!data.tooltipObject) {
-        // If tooltip has not yet been created (first hover), obtain it's
-        // contents and create the actual tooltip object.
-        if (!data.tooltip || !$.isPlainObject(data.tooltip)) {
-            content = $('<div/>').text(data.tooltip || $(this).attr('title')).html();
-        } else if (data.tooltip.html !== undefined) {
-            content = data.tooltip.html;
-        } else if (data.tooltip.text !== undefined) {
-            content = data.tooltip.text;
-        } else {
-            throw "Invalid content for tooltip via data";
-        }
-        if (!content) {
-            content = $(this).find('.tooltip-content').remove().html();
-        }
-        $(this).attr('title', null);
-        $(this).attr('data-tooltip', content);
-
-        tooltip = new STUDIP.Tooltip(x, y, content);
-
-        data.tooltipObject = tooltip;
-        $(this).attr('aria-describedby', tooltip.id);
-
-        $(this).on('remove', function() {
-            tooltip.remove();
-        });
-    } else if (visible) {
-        // If tooltip has already been created, update it's position.
-        // This is neccessary if the surrounding content is scrollable AND has
-        // been scrolled. Otherwise the tooltip would appear at it's previous
-        // and now wrong location.
-        data.tooltipObject.position(x, y);
-    }
-
-    if (visible) {
-        $('.studip-tooltip').not(data.tooltipObject).hide();
-        data.tooltipObject.show();
-    } else {
-        timeout = setTimeout(() => data.tooltipObject.hide(), delay);
-    }
-}).on('mouseenter focusin', '.studip-tooltip', () => {
-    clearTimeout(timeout);
-}).on('mouseleave focusout', '.studip-tooltip', function() {
-    $(this).hide();
-});
diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js
index 73b1aaa52e80b7617587b37b4af31cdcddfd0fc5..26834395e6d69b63c80b801f7f8346b20e59a847 100644
--- a/resources/assets/javascripts/entry-base.js
+++ b/resources/assets/javascripts/entry-base.js
@@ -55,7 +55,6 @@ import "./bootstrap/article.js"
 import "./bootstrap/copyable_links.js"
 import "./bootstrap/selection.js"
 import "./bootstrap/data_secure.js"
-import "./bootstrap/tooltip.js"
 import "./bootstrap/lightbox.js"
 import "./bootstrap/application.js"
 import "./bootstrap/global_search.js"
diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js
index fe20c9ae894c82b70f7dd15373f6c88d2cec6c48..36a72a5f3aea2b44f74b726767df2562c8a3ad54 100644
--- a/resources/assets/javascripts/init.js
+++ b/resources/assets/javascripts/init.js
@@ -76,7 +76,6 @@ import Statusgroups from './lib/statusgroups.js';
 import study_area_selection from './lib/study_area_selection.js';
 import Table from './lib/table.js';
 import TableOfContents from './lib/table-of-contents.js';
-import Tooltip from './lib/tooltip.js';
 import Tour from './lib/tour.js';
 import * as Gettext from './lib/gettext';
 import UserFilter from './lib/user_filter.js';
@@ -164,7 +163,6 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
     study_area_selection,
     Table,
     TableOfContents,
-    Tooltip,
     Tour,
     URLHelper,
     UserFilter,
diff --git a/resources/assets/javascripts/lib/tooltip.js b/resources/assets/javascripts/lib/tooltip.js
deleted file mode 100644
index 2cdac27be88aaa69b6fa0cdba5cfae2af8e9c826..0000000000000000000000000000000000000000
--- a/resources/assets/javascripts/lib/tooltip.js
+++ /dev/null
@@ -1,227 +0,0 @@
-import CSS from './css.js';
-
-/**
- * Tooltip library for Stud.IP
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @copyright Stud.IP Core Group 2014
- * @license GPL2 or any later version
- * @since Stud.IP 3.1
- */
-
-let count = 0;
-let threshold = 0;
-
-class Tooltip {
-    static get count() {
-        return count;
-    }
-
-    static set count(value) {
-        count = value;
-    }
-
-    // Threshold used for "edge detection" (imagine a padding along the edges)
-    static get threshold() {
-        return threshold;
-    }
-
-    static set threshold(value) {
-        threshold = value;
-    }
-
-    /**
-     * Returns a new unique id of a tooltip.
-     *
-     * @return {string} Unique id
-     * @static
-     */
-    static getId() {
-        const id = `studip-tooltip-${Tooltip.count}`;
-        Tooltip.count += 1;
-        return id;
-    }
-
-    /**
-     * Constructs a new tooltip at given location with given content.
-     * The applied css class may be changed by the fourth parameter.
-     *
-     * @class
-     * @classdesc Stud.IP tooltips provide an improved layout and handling
-     *            of contents (including html) than the browser's default
-     *            tooltip through title attribute would
-     *
-     * @param {int} x - Horizontal position of the tooltip
-     * @param {int} y - Vertical position of the tooltip
-     * @param {string} content - Content of the tooltip (may be html)
-     * @param {string} css_class - Optional name of the applied css class /
-     *                             defaults to 'studip-tooltip'
-     */
-    constructor(x, y, content, css_class) {
-        // Obtain unique id of the tooltip
-        this.id = Tooltip.getId();
-
-        // Create dom element of the tooltip, apply id and class and attach
-        // to dom
-        this.element = $('<div>');
-        this.element.addClass(css_class || 'studip-tooltip');
-        this.element.attr('id', this.id);
-        this.element.attr('role', 'tooltip');
-        this.element.appendTo('body');
-
-        // Set position and content and paint the tooltip
-        this.position(x, y);
-        this.update(content);
-        this.paint();
-    }
-
-    /**
-     * Translates the arrow(s) under a tooltip using css3 translate
-     * transforms. This is needed at the edges of the screen.
-     * This implies that a current browser is used. The translation could
-     * also be achieved by adjusting margins but that way we would need
-     * to hardcode values into this function since it's a struggle to
-     * obtain the neccessary values from the CSS pseudo selectors in JS.
-     *
-     * Internal, css rules are dynamically created and applied to the current
-     * document by using the methods provided in the file studip-css.js.
-     *
-     * @param {int} x - Horizontal offset
-     * @param {int} y - Vertical offset
-     */
-    translateArrows(x, y, left_arrow = false) {
-        CSS.removeRule(`#${this.id}::before`);
-        CSS.removeRule(`#${this.id}::after`);
-
-        if (x !== 0 || y !== 0) {
-            let before_rule = {
-                transform: `translate(${x}px, ${y}px);`
-            };
-            if (left_arrow) {
-                before_rule.transform = `translate(${x}px, ${y}px) rotate(90deg);`;
-            }
-            let after_rule = before_rule;
-            if (left_arrow) {
-                after_rule['border-width'] = '9px';
-            }
-            CSS.addRule(`#${this.id}::before`, before_rule, ['-ms-', '-webkit-']);
-            CSS.addRule(`#${this.id}::after`, after_rule, ['-ms-', '-webkit-']);
-        }
-    }
-
-    /**
-     * Updates the position of the tooltip.
-     *
-     * @param {int} x - Horizontal position of the tooltip
-     * @param {int} y - Vertical position of the tooltip
-     */
-    position(x, y) {
-        this.x = x;
-        this.y = y;
-    }
-
-    /**
-     * Updates the contents of the tooltip.
-     *
-     * @param {string} content - Content of the tooltip (may be html)
-     */
-    update(content) {
-        this.element.html(content);
-    }
-
-    /**
-     * "Paints" the tooltip. This method actually computes the dimensions of
-     * the tooltips, checks for screen edges and calculates the actual offset
-     * in the current document.
-     * This method is neccessary due to the fact that position and content
-     * can be changed apart from each other.
-     * Thus: Don't forget to repaint after adjusting any of the two.
-     */
-    paint() {
-        const width = this.element.outerWidth(true);
-        const height = this.element.outerHeight(true);
-        const maxWidth = $(document).width();
-        const maxHeight = $(document).height();
-        let x = this.x - width / 2;
-        let y = this.y - height;
-        //The arrow offset is the offset from the bottom right corner of
-        //the tooltip "frame".
-        let arrow_offset_x = 0;
-        let arrow_offset_y = 0;
-        let left_arrow = false;
-
-        if (y < 0) {
-            y = 0;
-            x = this.x + 20;
-            //Put the arrow on the left side and move the tooltip,
-            //if there is still enough place left on the right.
-            left_arrow = true;
-            arrow_offset_y = -height + this.y + 10;
-            if (arrow_offset_y > -20) {
-                y+= arrow_offset_y + 20;
-                arrow_offset_y = -20;
-            }
-            arrow_offset_x = -width / 2 - 8;
-        } else if (y + height > maxHeight) {
-            y = maxHeight - height;
-        }
-
-        if (x < 0) {
-            arrow_offset_x = 0;
-            x = 0;
-        } else if (x + width > maxWidth) {
-            arrow_offset_x = x + width - maxWidth;
-            x = maxWidth - width;
-        }
-        this.translateArrows(arrow_offset_x, arrow_offset_y, left_arrow);
-
-        this.element.css({
-            left: x,
-            top: y
-        });
-    }
-
-    /**
-     * Toggles the visibility of the tooltip. If no state is provided,
-     * the tooltip will be hidden if visible and vice versa. Pretty straight
-     * forward and no surprises here.
-     * This method implicitely calls paint before a tooltip is shown (in case
-     * it was forgotten).
-     *
-     * @param {bool} visible - Optional visibility parameter to set the
-     *                         tooltip to a certain state
-     */
-    toggle(visible) {
-        if (visible) {
-            this.paint();
-        }
-        this.element.toggle(visible);
-    }
-
-    /**
-     * Reveals the tooltip.
-     *
-     * @see Tooltip.toggle
-     */
-    show() {
-        this.toggle(true);
-    }
-
-    /**
-     * Hides the tooltip.
-     *
-     * @see Tooltip.toggle
-     */
-    hide() {
-        this.toggle(false);
-    }
-
-    /**
-     * Removes the tooltip
-     */
-    remove() {
-        this.element.remove();
-    }
-}
-
-export default Tooltip;
diff --git a/resources/assets/stylesheets/scss/tooltip.scss b/resources/assets/stylesheets/scss/tooltip.scss
index 4b7b36bbaffbfb769085c74e929393a4b22b8a4a..ac570a86388920572befecd8ad2fc993de08099e 100644
--- a/resources/assets/stylesheets/scss/tooltip.scss
+++ b/resources/assets/stylesheets/scss/tooltip.scss
@@ -7,7 +7,7 @@
     box-shadow: 0 1px 0 fade-out($white, 0.5) inset;
     font-size: var(--font-size-base);
     margin-bottom: 8px;
-    max-width: 230px;
+    max-width: $grid-element-width;
     padding: 10px;
     position: absolute;
     text-align: left;
@@ -38,11 +38,13 @@
         @extend %tooltip;
         display: none;
     }
-    &:hover .tooltip-content {
+
+    &:hover .tooltip-content,
+    &:focus .tooltip-content {
         bottom: 100%;
         display: inline-block;
         left: 50%;
-        margin-left: -129px;
-        width: 230px;
+        margin-left: - calc($grid-element-width / 2) - 10px;
+        width: $grid-element-width;
     }
 }
diff --git a/templates/shared/tooltip.php b/templates/shared/tooltip.php
index a97c73f275919a6d8639c8d074f8582a7d6673a3..8dbcd382421175fd5bd6b69a1e7a3690144d90f8 100644
--- a/templates/shared/tooltip.php
+++ b/templates/shared/tooltip.php
@@ -1,5 +1,4 @@
-<span class="tooltip tooltip-icon <? if ($important) echo 'tooltip-important'; ?>" data-tooltip <? if (!$html) printf('title="%s"', htmlReady($text)) ?> tabindex="0">
-<? if ($html): ?>
-    <span class="tooltip-content"><?= $text ?></span>
-<? endif; ?>
+<span class="tooltip tooltip-icon <? if ($important) echo 'tooltip-important'; ?>"
+      tabindex="0" aria-label="<?= $html ? kill_format($text) : htmlReady($text) ?>">
+    <span class="tooltip-content"><?= $html ? $text : htmlReady($text) ?></span>
 </span>