הסבר קצר שנפתח מעל המילה, עובר מתחתיה כשאין מקום למעלה, ונצמד פנימה כשהוא נוגע בקצה המסך. נפתח גם בפוקוס מקלדת ונסגר ב-Escape.
מתי משתמשים: טבלת מחירים, שדות בטופס, מונחים מקצועיים, דשבורד, תנאי שימוש. במקום להעמיס את העמוד בהסברים, הם נפתחים למי שביקש.
הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.
המחיר כולל וכן . התשלום מתבצע בשני חלקים, והשני משולם רק אחרי .
שימו לב שהמחיר אינו כולל .
הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --accent או --line משלו יורש אותם אוטומטית. הרכיב הזה מסתמך על dir="rtl".
/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.stage{padding:var(--sec, clamp(120px, 12vw, 260px)) var(--gutter, clamp(24px, 5vw, 96px));position:relative}
.stage.tight{padding-block:clamp(48px,5vw,90px)}
.stage.full{padding-inline:0}
/* B56 · טולטיפ נגיש שמתהפך ליד קצה המסך */
.tp-demo{max-width:min(720px,92vw);margin-inline:auto;font-size:17px;line-height:2.1;color:var(--muted, #6a6d85)}
.tp-demo p{margin:0 0 18px}
.tp{border-bottom:1.5px dashed var(--accent, #4a3aff);cursor:help;color:var(--ink, #16182b);font-weight:500;background:none;
border-inline:0;border-top:0;font-family:inherit;font-size:inherit;padding:0}
.tp:focus-visible{outline:2px solid var(--accent, #4a3aff);outline-offset:3px;border-radius:3px}
.tp-box{position:fixed;z-index:80;max-width:min(300px,80vw);background:var(--ink, #16182b);color:var(--bg, #f7f7fa);font-size:14px;
line-height:1.6;padding:11px 15px;border-radius:12px;box-shadow:0 14px 40px rgba(20,20,40,.28);
pointer-events:none;opacity:0;top:0;left:0}
.tp-box::after{content:"";position:absolute;width:11px;height:11px;background:var(--ink, #16182b);transform:rotate(45deg);
left:var(--ax,50%);margin-left:-5.5px}
.tp-box.top::after{bottom:-5px}
.tp-box.bottom::after{top:-5px}
.tp-row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:26px}
.tp-row .tp{border:1px solid var(--line, #e4e4ee);border-radius:999px;padding:9px 16px;background:var(--card, #fff)}
<div class="stage tight"><div class="tp-demo">
<p>המחיר כולל <button class="tp" data-tip="עד שלושה סבבי תיקונים על העיצוב, לפני המעבר לפיתוח.">שני סבבי תיקונים</button>
וכן <button class="tp" data-tip="חודש ראשון אחרי העלייה לאוויר: תיקוני באגים ושינויי טקסט קטנים, בלי עלות.">אחריות חודש</button>.
התשלום מתבצע בשני חלקים, והשני משולם רק אחרי <button class="tp" data-tip="הרגע שבו האתר חי בדומיין שלכם ואפשר להיכנס אליו מגוגל.">העלייה לאוויר</button>.</p>
<p>שימו לב שהמחיר אינו כולל <button class="tp" data-tip="צילום מקצועי, בנק תמונות בתשלום, או איורים בהזמנה. אפשר לחבר ספק בהמלצתנו.">חומרים ויזואליים</button>.</p>
<div class="tp-row">
<button class="tp" data-tip="נבדק בקצה השמאלי: הטולטיפ נצמד פנימה במקום לגלוש החוצה.">בדיקת קצה שמאל</button>
<button class="tp" data-tip="נבדק בקצה הימני: אותה הצמדה, בכיוון ההפוך.">בדיקת קצה ימין</button>
</div>
</div></div>
(function(){
const PAD=10,GAP=10;
const reduce=matchMedia("(prefers-reduced-motion: reduce)").matches;
const box=document.createElement("div");
box.className="tp-box";box.setAttribute("role","tooltip");box.id="tp-box";
document.body.appendChild(box);
let current=null;
function place(trigger){
box.textContent=trigger.dataset.tip;
box.classList.remove("top","bottom");
box.style.width="";
gsap.set(box,{opacity:0,x:0,y:0});
// נועלים את הרוחב שנמדד. בלי זה הרוחב בפועל יכול לצאת שונה מזה ששימש לחישוב,
// והקופסה גולשת מהמסך בכמה פיקסלים דווקא במסכים רחבים.
box.style.width=box.offsetWidth+"px";
const vw=document.documentElement.clientWidth; // בלי רוחב הגלילה, אחרת מפספסים כמה פיקסלים
const t=trigger.getBoundingClientRect(),b=box.getBoundingClientRect();
// אם אין מקום למעלה, עוברים למטה. זו ההחלטה הראשונה, כי היא משנה את הכל
const below=t.top-b.height-GAP<PAD;
const y=below?t.bottom+GAP:t.top-b.height-GAP;
// ואז מצמידים אופקית פנימה, כדי שהקופסה לא תגלוש מהמסך
let x=t.left+t.width/2-b.width/2;
x=Math.min(Math.max(PAD,x),vw-b.width-PAD);
box.classList.add(below?"bottom":"top");
gsap.set(box,{x:x,y:y});
// בדיקה חוזרת בכל זאת, כרשת ביטחון
let after=box.getBoundingClientRect();
for(let i=0;i<2;i++){
const a=box.getBoundingClientRect();
const dx=a.right>vw-PAD?(vw-PAD)-a.right:(a.left<PAD?PAD-a.left:0);
if(!dx)break;
x+=dx;gsap.set(box,{x:x});after=box.getBoundingClientRect();
}
// החץ מכוון למילה, אבל נשאר בתוך גבולות הקופסה גם כשהיא נצמדה לקצה
const ax=Math.min(Math.max(16,t.left+t.width/2-x),after.width-16);
box.style.setProperty("--ax",ax+"px");
gsap.to(box,{opacity:1,duration:reduce?0:.2,ease:"power2.out"});
trigger.setAttribute("aria-describedby","tp-box");
current=trigger;
}
function hide(){
if(current)current.removeAttribute("aria-describedby");
current=null;
gsap.to(box,{opacity:0,duration:reduce?0:.15});
}
document.querySelectorAll(".tp").forEach(t=>{
t.addEventListener("mouseenter",()=>place(t));
t.addEventListener("mouseleave",hide);
t.addEventListener("focus",()=>place(t));
t.addEventListener("blur",hide);
t.addEventListener("click",e=>{e.preventDefault();current===t?hide():place(t);}); // מגע
});
addEventListener("keydown",e=>{if(e.key==="Escape")hide();});
addEventListener("scroll",()=>{if(current)hide();},{passive:true});
})();