|$ curl https://forge-ai.dev/api/markdown?path=docs/components/tooltips
$cat docs/tooltips.md
updated Recently·10 min read·published
Tooltips
◆CSS◆HTML◆UI◆Intermediate
Pure CSS tooltips with positioning variants, rich content, and interactive hover triggers.
Position Variants
Tooltips positioned above, below, left, and right of the trigger element using CSS data attributes.
positions
Live
untitled.html
HTML
| 1 | <div class="wrapper"> |
| 2 | <div class="tooltip-wrap" data-tooltip="Top tooltip — short info"> |
| 3 | <button class="trigger"> |
| 4 | Top |
| 5 | </button> |
| 6 | </div> |
| 7 | <div class="tooltip-wrap bottom" data-tooltip="Bottom tooltip description"> |
| 8 | <button class="trigger"> |
| 9 | Bottom |
| 10 | </button> |
| 11 | </div> |
| 12 | <div class="tooltip-wrap left" data-tooltip="Left side"> |
| 13 | <button class="trigger"> |
| 14 | Left |
| 15 | </button> |
| 16 | </div> |
| 17 | <div class="tooltip-wrap right" data-tooltip="Right side"> |
| 18 | <button class="trigger"> |
| 19 | Right |
| 20 | </button> |
| 21 | </div> |
| 22 | </div> |
untitled.css
CSS
| 1 | .wrapper { |
| 2 | display:flex; |
| 3 | gap:24px; |
| 4 | align-items:center; |
| 5 | justify-content:center; |
| 6 | padding:48px 24px |
| 7 | } |
| 8 | .tooltip-wrap { |
| 9 | position:relative; |
| 10 | display:inline-flex |
| 11 | } |
| 12 | .tooltip-wrap::after { |
| 13 | content:attr(data-tooltip); |
| 14 | position:absolute; |
| 15 | bottom:calc(100% + 8px); |
| 16 | left:50%; |
| 17 | transform:translateX(-50%); |
| 18 | background:#1A1A1A; |
| 19 | color:#E0E0E0; |
| 20 | padding:6px 12px; |
| 21 | border-radius:6px; |
| 22 | font-size:11px; |
| 23 | font-family:system-ui, |
| 24 | sans-serif; |
| 25 | white-space:nowrap; |
| 26 | border:1px solid #333; |
| 27 | opacity:0; |
| 28 | pointer-events:none; |
| 29 | transition:opacity .2s ease; |
| 30 | z-index:10 |
| 31 | } |
| 32 | .tooltip-wrap:hover::after { |
| 33 | opacity:1 |
| 34 | } |
| 35 | .tooltip-wrap::before { |
| 36 | content:''; |
| 37 | position:absolute; |
| 38 | bottom:calc(100% + 4px); |
| 39 | left:50%; |
| 40 | transform:translateX(-50%); |
| 41 | border:5px solid transparent; |
| 42 | border-top-color:#333; |
| 43 | opacity:0; |
| 44 | pointer-events:none; |
| 45 | transition:opacity .2s ease; |
| 46 | z-index:10 |
| 47 | } |
| 48 | .tooltip-wrap:hover::before { |
| 49 | opacity:1 |
| 50 | } |
| 51 | .tooltip-wrap.bottom::after { |
| 52 | top:calc(100% + 8px); |
| 53 | bottom:auto |
| 54 | } |
| 55 | .tooltip-wrap.bottom::before { |
| 56 | top:calc(100% + 4px); |
| 57 | bottom:auto; |
| 58 | border-top-color:transparent; |
| 59 | border-bottom-color:#333 |
| 60 | } |
| 61 | .tooltip-wrap.left::after { |
| 62 | top:50%; |
| 63 | bottom:auto; |
| 64 | left:auto; |
| 65 | right:calc(100% + 8px); |
| 66 | transform:translateY(-50%) |
| 67 | } |
| 68 | .tooltip-wrap.left::before { |
| 69 | top:50%; |
| 70 | bottom:auto; |
| 71 | left:auto; |
| 72 | right:calc(100% + 4px); |
| 73 | transform:translateY(-50%); |
| 74 | border-top-color:transparent; |
| 75 | border-right-color:#333 |
| 76 | } |
| 77 | .tooltip-wrap.right::after { |
| 78 | top:50%; |
| 79 | bottom:auto; |
| 80 | left:calc(100% + 8px); |
| 81 | transform:translateY(-50%) |
| 82 | } |
| 83 | .tooltip-wrap.right::before { |
| 84 | top:50%; |
| 85 | bottom:auto; |
| 86 | left:calc(100% + 4px); |
| 87 | transform:translateY(-50%); |
| 88 | border-top-color:transparent; |
| 89 | border-left-color:#333 |
| 90 | } |
| 91 | .trigger { |
| 92 | padding:10px 20px; |
| 93 | border-radius:8px; |
| 94 | font-size:13px; |
| 95 | font-weight:600; |
| 96 | font-family:system-ui, |
| 97 | sans-serif; |
| 98 | cursor:pointer; |
| 99 | border:1px solid #333; |
| 100 | background:#1A1A1A; |
| 101 | color:#E0E0E0; |
| 102 | transition:all .2s |
| 103 | } |
| 104 | .trigger:hover { |
| 105 | background:#222; |
| 106 | border-color:#00FF41; |
| 107 | color:#00FF41 |
| 108 | } |
Rich Content
Tooltips with HTML content — multiple lines, colored text, and icons.
rich
Live
untitled.html
HTML
| 1 | <div class="wrapper"> |
| 2 | <div class="rich-tip"> |
| 3 | <button class="trigger"> |
| 4 | Hover me |
| 5 | </button> |
| 6 | <div class="rich-content"> |
| 7 | <div class="rich-header"> |
| 8 | <svg viewBox="0 0 24 24" fill="none" stroke="#00FF41" stroke-width="2" width="14" height="14"> |
| 9 | <circle cx="12" cy="12" r="10"/> |
| 10 | <line x1="12" y1="16" x2="12" y2="12"/> |
| 11 | <line x1="12" y1="8" x2="12.01" y2="8"/> |
| 12 | </svg> |
| 13 | <span> |
| 14 | Information |
| 15 | </span> |
| 16 | </div> |
| 17 | <p class="rich-body"> |
| 18 | This tooltip supports multiple lines of content with detailed explanations and visual elements. |
| 19 | </p> |
| 20 | <div class="rich-footer"> |
| 21 | <span class="badge"> |
| 22 | Updated |
| 23 | </span> |
| 24 | <span> |
| 25 | 2 mins ago |
| 26 | </span> |
| 27 | </div> |
| 28 | </div> |
| 29 | </div> |
| 30 | <div class="rich-tip left"> |
| 31 | <button class="trigger"> |
| 32 | Details |
| 33 | </button> |
| 34 | <div class="rich-content"> |
| 35 | <div class="rich-header"> |
| 36 | <svg viewBox="0 0 24 24" fill="none" stroke="#00FF41" stroke-width="2" width="14" height="14"> |
| 37 | <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/> |
| 38 | </svg> |
| 39 | <span> |
| 40 | Status |
| 41 | </span> |
| 42 | </div> |
| 43 | <p class="rich-body"> |
| 44 | All systems operational. No incidents reported in the last 24 hours. |
| 45 | </p> |
| 46 | </div> |
| 47 | </div> |
| 48 | </div> |
untitled.css
CSS
| 1 | .wrapper { |
| 2 | display:flex; |
| 3 | gap:40px; |
| 4 | align-items:flex-start; |
| 5 | justify-content:center; |
| 6 | padding:48px 24px |
| 7 | } |
| 8 | .rich-tip { |
| 9 | position:relative; |
| 10 | display:inline-flex |
| 11 | } |
| 12 | .rich-content { |
| 13 | position:absolute; |
| 14 | bottom:calc(100% + 12px); |
| 15 | left:50%; |
| 16 | transform:translateX(-50%); |
| 17 | background:#151515; |
| 18 | color:#E0E0E0; |
| 19 | padding:14px; |
| 20 | border-radius:8px; |
| 21 | font-size:11px; |
| 22 | font-family:system-ui, |
| 23 | sans-serif; |
| 24 | border:1px solid #333; |
| 25 | min-width:220px; |
| 26 | opacity:0; |
| 27 | pointer-events:none; |
| 28 | transition:opacity .2s ease; |
| 29 | z-index:10; |
| 30 | box-shadow:0 8px 24px rgba(0, |
| 31 | 0, |
| 32 | 0, |
| 33 | .4) |
| 34 | } |
| 35 | .rich-tip:hover .rich-content { |
| 36 | opacity:1 |
| 37 | } |
| 38 | .rich-tip.left .rich-content { |
| 39 | left:auto; |
| 40 | right:calc(100% + 12px); |
| 41 | bottom:auto; |
| 42 | top:50%; |
| 43 | transform:translateY(-50%) |
| 44 | } |
| 45 | .rich-header { |
| 46 | display:flex; |
| 47 | align-items:center; |
| 48 | gap:6px; |
| 49 | font-weight:600; |
| 50 | font-size:12px; |
| 51 | margin-bottom:8px; |
| 52 | padding-bottom:8px; |
| 53 | border-bottom:1px solid #2A2A2A |
| 54 | } |
| 55 | .rich-body { |
| 56 | color:#A0A0A0; |
| 57 | line-height:1.5; |
| 58 | margin-bottom:8px |
| 59 | } |
| 60 | .rich-footer { |
| 61 | display:flex; |
| 62 | align-items:center; |
| 63 | gap:8px; |
| 64 | font-size:10px; |
| 65 | color:#666; |
| 66 | border-top:1px solid #2A2A2A; |
| 67 | padding-top:8px |
| 68 | } |
| 69 | .badge { |
| 70 | background:#00FF41/10; |
| 71 | color:#00FF41; |
| 72 | padding:2px 6px; |
| 73 | border-radius:4px; |
| 74 | font-size:9px; |
| 75 | border:1px solid #00FF41/20 |
| 76 | } |
| 77 | .trigger { |
| 78 | padding:10px 20px; |
| 79 | border-radius:8px; |
| 80 | font-size:13px; |
| 81 | font-weight:600; |
| 82 | font-family:system-ui, |
| 83 | sans-serif; |
| 84 | cursor:pointer; |
| 85 | border:1px solid #333; |
| 86 | background:#1A1A1A; |
| 87 | color:#E0E0E0; |
| 88 | transition:all .2s |
| 89 | } |
| 90 | .trigger:hover { |
| 91 | background:#222; |
| 92 | border-color:#00FF41; |
| 93 | color:#00FF41 |
| 94 | } |
Interactive Delay
Tooltips with show/hide delay for better UX — instant on desktop, delayed on mobile.
delay
Live
untitled.html
HTML
| 1 | <div class="wrapper"> |
| 2 | <div class="delay-tip" data-delay="200"> |
| 3 | <button class="trigger"> |
| 4 | 200ms delay |
| 5 | </button> |
| 6 | <span class="delay-msg"> |
| 7 | Appears after 200ms |
| 8 | </span> |
| 9 | </div> |
| 10 | <div class="delay-tip" data-delay="500"> |
| 11 | <button class="trigger"> |
| 12 | 500ms delay |
| 13 | </button> |
| 14 | <span class="delay-msg"> |
| 15 | Appears after 500ms |
| 16 | </span> |
| 17 | </div> |
| 18 | <div class="delay-tip" data-duration="3000"> |
| 19 | <button class="trigger"> |
| 20 | Auto-hide 3s |
| 21 | </button> |
| 22 | <span class="delay-msg"> |
| 23 | Hides after 3 seconds |
| 24 | </span> |
| 25 | </div> |
| 26 | </div> |
untitled.css
CSS
| 1 | .wrapper { |
| 2 | display:flex; |
| 3 | gap:24px; |
| 4 | align-items:center; |
| 5 | justify-content:center; |
| 6 | padding:48px 24px |
| 7 | } |
| 8 | .delay-tip { |
| 9 | position:relative; |
| 10 | display:inline-flex |
| 11 | } |
| 12 | .delay-msg { |
| 13 | position:absolute; |
| 14 | bottom:calc(100% + 8px); |
| 15 | left:50%; |
| 16 | transform:translateX(-50%); |
| 17 | background:#1A1A1A; |
| 18 | color:#E0E0E0; |
| 19 | padding:6px 12px; |
| 20 | border-radius:6px; |
| 21 | font-size:11px; |
| 22 | font-family:system-ui, |
| 23 | sans-serif; |
| 24 | white-space:nowrap; |
| 25 | border:1px solid #333; |
| 26 | opacity:0; |
| 27 | pointer-events:none; |
| 28 | transition:opacity .3s ease; |
| 29 | z-index:10 |
| 30 | } |
| 31 | .delay-tip:hover .delay-msg { |
| 32 | opacity:1 |
| 33 | } |
| 34 | .trigger { |
| 35 | padding:10px 20px; |
| 36 | border-radius:8px; |
| 37 | font-size:13px; |
| 38 | font-weight:600; |
| 39 | font-family:system-ui, |
| 40 | sans-serif; |
| 41 | cursor:pointer; |
| 42 | border:1px solid #333; |
| 43 | background:#1A1A1A; |
| 44 | color:#E0E0E0; |
| 45 | transition:all .2s |
| 46 | } |
| 47 | .trigger:hover { |
| 48 | background:#222; |
| 49 | border-color:#00FF41; |
| 50 | color:#00FF41 |
| 51 | } |
untitled.js
JavaScript
| 1 | document.querySelectorAll('.delay-tip').forEach(el=>{const msg=el.querySelector('.delay-msg');const delay=parseInt(el.dataset.delay)||0;const duration=parseInt(el.dataset.duration)||0;let timer;el.addEventListener('mouseenter',()=>{clearTimeout(timer);timer=setTimeout(()=>{msg.style.opacity='1'},delay)});el.addEventListener('mouseleave',()=>{clearTimeout(timer);msg.style.opacity='0'});if(duration){el.addEventListener('mouseenter',()=>{setTimeout(()=>{msg.style.opacity='0'},duration+delay)})}}) |