Tooltip

Small explanatory overlay.

Saves your changes to the cloud.

html

<div class="tooltip-demo">
  <button type="button" class="tooltip-trigger" aria-describedby="tooltip-1">Save changes</button>
  <span role="tooltip" id="tooltip-1" class="tooltip tooltip--top tooltip--visible" style="max-width:180px;background:#241430;">
    Saves your changes to the cloud.
    <span class="tooltip__arrow" style="background:#241430;" aria-hidden="true"></span>
  </span>
</div>
<style>
.tooltip-demo { position: relative; min-height: 200px; display: flex; align-items: center; justify-content: center; box-sizing: border-box; }
.tooltip-trigger { font: 500 14px system-ui, sans-serif; padding: 9px 16px; border-radius: 8px; border: 1.5px solid #24143033; background: #FAF7F2; color: #241430; cursor: pointer; }
.tooltip-trigger:focus-visible { outline: 2px solid #241430; outline-offset: 2px; }
.tooltip { position: absolute; color: #FAF7F2; font: 500 13px/1.4 system-ui, sans-serif; padding: 7px 11px; border-radius: 8px; box-shadow: 0 10px 22px rgba(36,20,48,.24); opacity: 0; visibility: hidden; pointer-events: none; text-align: center; box-sizing: border-box; }
@media (prefers-reduced-motion: no-preference) {
  .tooltip { transition: opacity .15s ease; }
}
.tooltip-trigger:hover + .tooltip, .tooltip-trigger:focus-visible + .tooltip, .tooltip--visible { opacity: 1; visibility: visible; }
.tooltip--top { bottom: calc(50% + 34px); left: 50%; transform: translateX(-50%); }
.tooltip--bottom { top: calc(50% + 34px); left: 50%; transform: translateX(-50%); }
.tooltip--right { left: calc(50% + 70px); top: 50%; transform: translateY(-50%); }
.tooltip__arrow { position: absolute; width: 8px; height: 8px; transform: rotate(45deg); }
.tooltip--top .tooltip__arrow { bottom: -4px; left: 50%; margin-left: -4px; }
.tooltip--bottom .tooltip__arrow { top: -4px; left: 50%; margin-left: -4px; }
.tooltip--right .tooltip__arrow { left: -4px; top: 50%; margin-top: -4px; }
</style>

tailwind

<div class="relative min-h-[200px] flex items-center justify-center box-border">
  <button type="button" aria-describedby="tooltip-tw-1" class="px-4 py-2.5 rounded-lg text-sm font-medium border-[1.5px] border-[#24143033] bg-[#FAF7F2] text-[#241430]">Save changes</button>
  <span role="tooltip" id="tooltip-tw-1" class="tooltip-tw tooltip-tw--top tooltip-tw--visible absolute text-[#FAF7F2] text-[13px] leading-snug px-2.5 py-1.5 rounded-lg shadow-2xl text-center box-border" style="max-width:180px;background:#241430;">
    Saves your changes to the cloud.
    <span aria-hidden="true" class="tooltip-tw__arrow absolute w-2 h-2 rotate-45" style="background:#241430;"></span>
  </span>
</div>
<style>
.tooltip-tw { opacity: 0; visibility: hidden; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .tooltip-tw { transition: opacity .15s ease; }
}
button:hover + .tooltip-tw, button:focus-visible + .tooltip-tw, .tooltip-tw--visible { opacity: 1; visibility: visible; }
.tooltip-tw--top { bottom: calc(50% + 34px); left: 50%; transform: translateX(-50%); }
.tooltip-tw--bottom { top: calc(50% + 34px); left: 50%; transform: translateX(-50%); }
.tooltip-tw--right { left: calc(50% + 70px); top: 50%; transform: translateY(-50%); }
.tooltip-tw--top .tooltip-tw__arrow { bottom: -4px; left: 50%; margin-left: -4px; }
.tooltip-tw--bottom .tooltip-tw__arrow { top: -4px; left: 50%; margin-left: -4px; }
.tooltip-tw--right .tooltip-tw__arrow { left: -4px; top: 50%; margin-top: -4px; }
</style>

react

import { useState } from "react";

export function Tooltip({
  label = "Save changes",
  text = "Saves your changes to the cloud.",
  placement = "top",
  accent = "#241430",
  width = 180,
  visible: initialVisible = true,
}) {
  const [hovered, setHovered] = useState(false);
  const visible = hovered || initialVisible;
  const posStyle = {
    top: { bottom: "calc(50% + 34px)", left: "50%", transform: "translateX(-50%)" },
    bottom: { top: "calc(50% + 34px)", left: "50%", transform: "translateX(-50%)" },
    right: { left: "calc(50% + 70px)", top: "50%", transform: "translateY(-50%)" },
  }[placement] ?? {};
  const arrowStyle = {
    top: { bottom: -4, left: "50%", marginLeft: -4 },
    bottom: { top: -4, left: "50%", marginLeft: -4 },
    right: { left: -4, top: "50%", marginTop: -4 },
  }[placement] ?? {};
  return (
    <div style={{ position: "relative", minHeight: 200, display: "flex", alignItems: "center", justifyContent: "center", boxSizing: "border-box" }}>
      <button type="button" aria-describedby="tooltip-1"
        onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}
        onFocus={() => setHovered(true)} onBlur={() => setHovered(false)}
        style={{ font: "500 14px system-ui, sans-serif", padding: "9px 16px", borderRadius: 8,
        border: "1.5px solid #24143033", background: "#FAF7F2", color: "#241430", cursor: "pointer" }}>
        {label}
      </button>
      <span role="tooltip" id="tooltip-1" className="tooltip-bubble" style={{ position: "absolute", color: "#FAF7F2", fontSize: 13,
        lineHeight: 1.4, fontWeight: 500, padding: "7px 11px", borderRadius: 8, boxShadow: "0 10px 22px rgba(36,20,48,.24)",
        opacity: visible ? 1 : 0, visibility: visible ? "visible" : "hidden",
        pointerEvents: "none", textAlign: "center", boxSizing: "border-box", maxWidth: width, background: accent, ...posStyle }}>
        {text}
        <span aria-hidden="true" style={{ position: "absolute", width: 8, height: 8, transform: "rotate(45deg)", background: accent, ...arrowStyle }} />
      </span>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .tooltip-bubble { transition: opacity .15s ease; }
        }
      `}</style>
    </div>
  );
}