UIDex

التلميح

Tooltip

role="tooltip" + aria-describedby

اسمه كمانتولتيب، تلميح الأدوات، فقاعة التلميح، تلميح عائم، شرح الأيقونة، hover hint، info bubble، help tip، title tip، hover label

التلميح نصّ قصير يظهر بجوار عنصر عند تمرير المؤشّر فوقه أو عند وصول لوحة المفاتيح إليه، فيسمّي العنصر أو يشرح ما يفعله. وهو وصف لا محتوى: نصّ خالص بلا زرّ ولا رابط ولا حقل، لأن role="tooltip" لا يعرّف أي نموذج للتنقّل داخله، وأوّل عنصر تفاعلي تضعه فيه يحوّله إلى شيء آخر. وثلاثة أشياء تشبهه ويجب أن تميّزه عنها: بطاقة التمرير (hover card) طبقة غنيّة قابلة للتفاعل تحمل صوراً وروابط، وهي مصمّمة أصلاً ليدخلها المؤشّر ويستقرّ فيها؛ و toggletip يُفتح بالنقر لا بالمرور، ومحتواه يُعلَن عبر منطقة aria-live لأنه إجابة على سؤال طرحه المستخدم بنفسه؛ والخاصية الأصلية title في HTML تلميح يرسمه المتصفّح، بتأخير لا تملك ضبطه ومظهر لا تملك تنسيقه، ولا يظهر للوحة المفاتيح ولا للمس أصلاً. وتترتّب على ذلك قاعدتان: التلميح لا يحتوي أبداً على عنصر تفاعلي، ولا يكون أبداً المكان الوحيد الذي توجد فيه المعلومة. فنصف مستخدميك على شاشة لمس لا يوجد فيها «تمرير مؤشّر» من الأساس.

لو قلت عليه…

«الكلام اللي بيطلع لما أحطّ الماوس على الأيقونة»«الفقاعة الصغيرة اللي بتشرح الزرار»«المستطيل اللي بيظهر تحت الأيقونة وفيه سهم»«الشرح اللي بيختفي أول ما أشيل إيدي»«الحتّة اللي بتوريني الاسم كامل لما يكون مقصوص»

العيّنة الحيّة

تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.

مرّر المؤشّر فوق أيقونة الرابط، أو اضغط Tab للوصول إليها.

نسخ الرابط إلى الحافظةCtrl + Shift + C
aria-describedby="tip-share"تأخير الفتح450ms
RTLالاختصار اللاتيني معزول باتجاهه، و Esc يُغلق التلميح

تشريح العنصر: كل جزء واسمه

مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.

برومبت البناء

ابنِ تلميحاً (tooltip) فوق مشغّل قابل للتركيز أصلاً، أي <button> أو رابط، لا <div> بـ tabindex. صيّر الصندوق بـ role="tooltip" ومعرّف ثابت، واربطه بالمشغّل بـ aria-describedby يُضاف عند الفتح ويُزال عند الإغلاق، ولا تضع aria-label بنفس النصّ حتى لا يُنطق مرّتين. المحتوى نصّ فقط: ممنوع أي زرّ أو رابط أو حقل داخله، وممنوع أن يكون التلميح المكان الوحيد للمعلومة لأن أجهزة اللمس بلا تمرير مؤشّر أصلاً، فاعرض نفس المعلومة كنصّ ظاهر أو في لوحة مساعدة. افتحه بعد تأخير ‎450ms عند التمرير، وفوراً عند focus، وأغلقه عند blur و pointerleave و Escape عبر مستمع على المستند يُزال مع الإغلاق. اجعل المسافة بين المشغّل والصندوق جزءاً من منطقة التمرير، بحشو شفّاف أو safePolygon، حتى لا يُغلق أثناء انتقال المؤشّر إليه. ضعه في portal ليفلت من overflow: hidden، واحسب موضعه بـ flip و shift و arrow. اكتب كل الموضعة بخصائص منطقية: inset-inline-start و margin-inline و padding-inline، والمحاذاة بلاحقة ‎-start/-end لا بـ left/right. والاستثناء الوحيد إزاحة السهم القادمة من arrow()، فهي تُطبَّق بـ left لأنها محسوبة فيزيائياً. عرّف transform-origin من متغيّر يُقلب داخل [dir="rtl"] لأنه لا يقبل كلمات منطقية، ولُفّ أي رمز لاتيني داخل نصّ عربي بـ <bdi>. احترم prefers-reduced-motion بإلغاء الحركة مع إبقاء التأخير.

سلوكه في الاتجاه من اليمين لليسار

الجزء ده حصري عندنا.

الجهة والمحاذاة والانقلاب

ينعكس

قيم الجهة الرئيسية في Floating UI و Radix (top و right و bottom و left) فيزيائية لا تعرف اتجاه الصفحة: تلميح مضبوط على right يبقى على يمين المشغّل حتى في صفحة عربية، فاشتقّها بنفسك من نيّة منطقية. أمّا لاحقة المحاذاة ‎-start و ‎-end فمنطقية فعلاً؛ computePosition يقرأ direction من الأنماط المحسوبة ويقلبها، فـ bottom-start يحاذي الحافّة اليمنى في RTL. والانقلاب عند اصطدام حافّة النافذة شأن middleware الـ flip لا شأن الاتجاه، وأي تثبيت يدوي يُكتب بـ inset-inline-start لا بـ left.

إزاحة السهم

لا ينعكس

القيمة الخارجة من middlewareData.arrow.x بكسلات فيزيائية مقيسة من الحافّة اليسرى لصندوق التلميح، وقد حُسبت بعد أن أخذ المحرّك اتجاه الصفحة في اعتباره، فتُطبَّق حرفياً بـ left: Xpx؛ ولو كتبتها inset-inline-start انقلبت مرّة ثانية وانفصل السهم عن المشغّل في RTL. والوجه الذي يلتصق به السهم يُشتقّ بدوره من الجهة الفيزيائية في placement، فمقابل top هو bottom ومقابل left هو right، فيبقى فيزيائياً هو الآخر، وكتابته بخاصية منطقية تفكّ ارتباطه بالجهة التي حسبها المحرّك فعلاً. وهذه من المرّات النادرة التي تكون فيها الخاصية الفيزيائية هي الصحيحة، وتبقى القاعدة العامة (inset-inline-start بدل left) سارية على كل ما عداهما في التلميح.

اتجاه نصّ التلميح

لا ينعكس

اتجاه النصّ داخل التلميح يتبع لغة النصّ نفسه لا لغة الصفحة: تلميح عربي يشرح اسم دالّة مثل getUserById أو مساراً مثل ‎/var/log/app.log لا بدّ أن يعزل الجزء اللاتيني بـ <bdi> أو بـ dir="ltr" مع unicode-bidi: isolate، وإلا قفزت الشرطة المائلة الأولى إلى الطرف الآخر. والفخّ الأكبر أن التلميح يُصيَّر عادةً في portal تحت <body>، فيرث dir من جذر الصفحة لا من مشغّله؛ لو كان مشغّلك داخل كتلة كود بـ dir="ltr" فالصندوق لن يعرف ذلك أبداً. مرّر dir صراحةً على محتوى التلميح، أو اضبطه من DirectionProvider في Radix.

منشأ حركة الظهور

ينعكس

حركة التكبير يجب أن تنطلق من الجهة التي فيها المشغّل، وإلا بدا التلميح كأنه ينمو من نقطة عشوائية. والفخّ أن transform-origin لا يقبل كلمات منطقية إطلاقاً: inline-start ليست قيمة صالحة، والقيم المسموحة كلها فيزيائية أو نسب مئوية تُقاس من الحافّة اليسرى العليا مهما كان الاتجاه. اضبطها من متغيّر: ‎--tip-origin: right؛ ثم ‎[dir="rtl"] & { --tip-origin: left } وطبّق transform-origin: var(--tip-origin) center، وعامِل أيّ translateX داخل الحركة بالطريقة نفسها.

الخاصية الأصلية title

لا ينعكس

التلميح المبني على title يرسمه المتصفّح خارج شجرة الـ DOM: لا تصله CSS، ولا تستطيع لفّ جزء منه بـ <bdi> لأن قيمة الخاصية نصّ خام لا يقبل أي وسم. فسطر مختلط مثل «افتح ‎config.json من مجلّد build» يُرتَّب بخوارزمية bidi وحدها، وقد ينتهي الاسم اللاتيني أو النقطة أو الشرطة المائلة في الطرف الخطأ. والحيلة الوحيدة الباقية داخل نصّ خام هي محارف التحكّم نفسها: U+2066 و U+2069 للعزل، أو U+200E. والأصحّ أن تستبدل title بعنصر role="tooltip" من عندك، تعمل فيه dir و <bdi> ويراه مستخدم لوحة المفاتيح واللمس.

أسماؤه في الكود

كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.

ARIArole="tooltip" + aria-describedbyالدور يصف الصندوق، و describedby هو ما يجعل قارئ الشاشة ينطقه بعد اسم العنصر.
HTMLtitle="…"التلميح الأصلي: لا يقبل تنسيقاً ولا يظهر للوحة المفاتيح ولا للمس، وهو مرجع تاريخي لا خيار تصميم.
Radix UI<Tooltip.Provider delayDuration={450}>التأخير يُضبط على المزوّد لا على كل تلميح، و skipDelayDuration يلغيه بين تلميحات المجموعة الواحدة.
Floating UIuseHover(ctx, { handleClose: safePolygon() })يبقي التلميح مفتوحاً ما دام المؤشّر متّجهاً نحوه، بدل الاعتماد على مهلة إغلاق عمياء.
shadcn/ui<TooltipTrigger asChild>asChild يمرّر السلوك إلى زرّك الحقيقي بدل لفّه بعنصر إضافي غير قابل للتركيز.
Material UI<Tooltip title enterDelay leaveDelay>ماتيريال تسمّي النصّ title لكنه عنصر React كامل، ومهلتا الفتح والإغلاق منفصلتان عمداً.
CSSanchor-name / position-areaالموضعة المرتكزة الأصلية؛ قيم position-area المنطقية مثل block-start تتبع اتجاه الكتابة وحدها.

شوف كمان

آخر تحديث · 2026-08-19