Material Design Inspired Bubble Tooltips with Pure CSS

Category: CSS & CSS3 , Tooltip | April 28, 2026
AuthorGiedr-Ju
Last UpdateApril 28, 2026
LicenseMIT
Tags
Views0 views
Material Design Inspired Bubble Tooltips with Pure CSS

A pure CSS tooltip library that creates Material-style bubble tooltips with ::before, ::after, and HTML data attributes.

Features

  • Renders tooltip text from the data-tip attribute value using the CSS attr() function on the ::before pseudo-element.
  • 4 placement directions: top (default), bottom, left, and right.
  • 4 color variants: default, accent, info, and danger.
  • Activates on both mouse hover and keyboard focus states for full accessibility.
  • Applies a short fade-and-slide transition on tooltip entry and exit.
  • Supports standard block buttons, icon buttons, and inline text spans with multi-line layout.

How to use it:

1. Attach data-tip to any element. The attribute value becomes the visible tooltip text.

<!-- Default tooltip — appears above the element -->
<button class="btn" data-tip="Save your changes">Save</button>

2. The data-pos attribute sets which side the tooltip appears on. Defaults to the top position.

<!-- Tooltip appears below the element -->
<button class="btn" data-tip="Revert to the last saved version" data-pos="bottom">Revert</button>
<!-- Tooltip appears to the left -->
<button class="btn" data-tip="Return to previous step" data-pos="left">Back</button>
<!-- Tooltip appears to the right -->
<button class="btn" data-tip="Open live preview" data-pos="right">Preview</button>

3. The data-variant attribute switches the tooltip’s color scheme.

<!-- Accent: yellow background with dark text -->
<button class="btn" data-tip="Recommended next action" data-variant="accent">Publish</button>
<!-- Danger: red background with white text -->
<button class="btn" data-tip="This action cannot be undone" data-variant="danger">Delete</button>
<!-- Info: blue background with white text -->
<button class="btn" data-tip="View the full documentation" data-variant="info">Help</button>

4. Copy this CSS into your stylesheet. The bubble comes from ::before. The arrow comes from ::after.

/* Shared sizing rule for tooltip elements and pseudo-elements. */
*, *::before, *::after {
  box-sizing: border-box;
}
/* Theme tokens control the tooltip surface, text, border, and radius. */
:root {
  --tip-bg: #1e1e1e;
  --tip-text: #f0f0f0;
  --tip-border: #333;
  --tip-accent: #e8ff47;
  --tip-danger: #ff4444;
  --tip-info: #2563eb;
  --tip-radius: 15px;
}
/* Every element with tooltip text becomes the positioning context. */
[data-tip] {
  --tip-active-bg: var(--tip-bg);
  --tip-active-text: var(--tip-text);
  --tip-active-border: var(--tip-border);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: default;
}
/* The bubble reads its text from the HTML attribute. */
[data-tip]::before {
  content: attr(data-tip);
  position: absolute;
  z-index: 10;
  background: var(--tip-active-bg);
  color: var(--tip-active-text);
  border: 1px solid var(--tip-active-border);
  border-radius: var(--tip-radius);
  padding: 6px 12px;
  font-family: "DM Mono", monospace;
  font-size: 0.72rem;
  font-weight: 400;
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
/* The arrow starts as a zero-size triangle. */
[data-tip]::after {
  content: "";
  position: absolute;
  z-index: 11;
  width: 0;
  height: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
/* Hover and keyboard focus reveal both pseudo-elements. */
[data-tip]:hover::before,
[data-tip]:focus::before,
[data-tip]:hover::after,
[data-tip]:focus::after {
  opacity: 1;
}
/* Keep the browser focus ring under your own UI control. */
[data-tip]:focus {
  outline: none;
}
/* Top placement acts as the default placement. */
[data-tip]:not([data-pos])::before,
[data-tip][data-pos="top"]::before {
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
}
[data-tip]:not([data-pos])::after,
[data-tip][data-pos="top"]::after {
  bottom: calc(100% + 4px);
  left: 50%;
  transform:translateX(-50%) translateY(4px);
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 6px solid var(--tip-active-border);
}
[data-tip]:not([data-pos]):hover::before,
[data-tip]:not([data-pos]):focus::before,
[data-tip]:not([data-pos]):hover::after,
[data-tip]:not([data-pos]):focus::after,
[data-tip][data-pos="top"]:hover::before,
[data-tip][data-pos="top"]:focus::before,
[data-tip][data-pos="top"]:hover::after,
[data-tip][data-pos="top"]:focus::after {
  transform: translateX(-50%) translateY(0);
}
/* Bottom placement moves the bubble under the target. */
[data-tip][data-pos="bottom"]::before {
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
}
[data-tip][data-pos="bottom"]::after {
  top: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 6px solid var(--tip-active-border);
}
[data-tip][data-pos="bottom"]:hover::before,
[data-tip][data-pos="bottom"]:focus::before,
[data-tip][data-pos="bottom"]:hover::after,
[data-tip][data-pos="bottom"]:focus::after {
  transform: translateX(-50%) translateY(0);
}
/* Left placement moves the bubble before the target. */
[data-tip][data-pos="left"]::before {
  right: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%) translateX(4px);
}
[data-tip][data-pos="left"]::after {
  right: calc(100% + 4px);
  top: 50%;
  transform: translateY(-50%) translateX(4px);
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 6px solid var(--tip-active-border);
}
[data-tip][data-pos="left"]:hover::before,
[data-tip][data-pos="left"]:focus::before,
[data-tip][data-pos="left"]:hover::after,
[data-tip][data-pos="left"]:focus::after {
  transform: translateY(-50%) translateX(0);
}
/* Right placement moves the bubble after the target. */
[data-tip][data-pos="right"]::before {
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%) translateX(-4px);
}
[data-tip][data-pos="right"]::after {
  left: calc(100% + 4px);
  top: 50%;
  transform: translateY(-50%) translateX(-4px);
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-right: 6px solid var(--tip-active-border);
}
[data-tip][data-pos="right"]:hover::before,
[data-tip][data-pos="right"]:focus::before,
[data-tip][data-pos="right"]:hover::after,
[data-tip][data-pos="right"]:focus::after {
  transform: translateY(-50%) translateX(0);
}
/* Accent variant for positive or highlighted actions. */
[data-tip][data-variant="accent"] {
  --tip-active-bg: var(--tip-accent);
  --tip-active-text: #0d0d0d;
  --tip-active-border: var(--tip-accent);
}
/* Danger variant for destructive actions. */
[data-tip][data-variant="danger"] {
  --tip-active-bg: var(--tip-danger);
  --tip-active-text: #fff;
  --tip-active-border: var(--tip-danger);
}
/* Info variant for neutral help text. */
[data-tip][data-variant="info"] {
  --tip-active-bg: var(--tip-info);
  --tip-active-text: #fff;
  --tip-active-border: var(--tip-info);
}

Alternatives

You Might Be Interested In:


Leave a Reply