{"id":"l-a11y-2-2","courseId":"web-accessibility","lang":"ar","kind":"lesson","summary":"اختر بشكل صحيح بين الرابط والزر، واستبدل عناصر div القابلة للنقر وروابط href=“#” بعناصر أصلية تعمل لمستخدمي لوحة المفاتيح وقارئ الشاشة.","html":"<p>يبدو عنصر “Add to basket” في Ticketly كأنه زر: زوايا مستديرة، ولون العلامة التجارية، ومؤشّر يد عند التمرير فوقه. لكنه <code dir=\"ltr\">div</code> عليه <code dir=\"ltr\">onclick</code>. انقره بالفأرة فيعمل بإتقان، ولهذا بالضبط لم ينتبه أحد في الفريق الأصلي. اضغط Tab فيقفز التركيز متجاوزًا إياه. يقرؤه قارئ شاشة سمير كنص عادي. ومستخدم جهاز التبديل لا يستطيع الوصول إليه أصلًا.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الروابط تنقلك، والأزرار تفعل</h2>\n<p>الاختيار بين عنصري التحكّم الأصليين يعود إلى سؤال واحد: <strong>هل يأخذ المستخدم إلى مكان ما؟</strong></p>\n<ul>\n<li><strong>الرابط</strong> (<code dir=\"ltr\">&lt;a href=&quot;…&quot;&gt;</code>) ينقلك: إلى صفحة أخرى، أو جزء آخر من هذه الصفحة (<code dir=\"ltr\">#lineup</code>)، أو ملف، أو عنوان بريد إلكتروني. تُعلنه قارئات الشاشة على أنه “link”. ويتوقّع المستخدمون أن يفتحوه في تبويب جديد، وأن ينسخوا عنوانه، وأن يروه في سجلّ التصفّح. ويُفعَّل بمفتاح <strong>Enter</strong>.</li>\n<li><strong>الزر</strong> (<code dir=\"ltr\">&lt;button&gt;</code>) ينفّذ فعلًا في الصفحة الحالية: إضافة إلى السلة، أو فتح نافذة حوار، أو إرسال نموذج، أو تشغيل فلتر وإيقافه. تُعلنه قارئات الشاشة على أنه “button”. ويُفعَّل بمفتاحي <strong>Enter وSpace</strong>.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-a11y-2-2-t1\"><defs><marker id=\"arrow-l-a11y-2-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-a11y-2-2-t1\">مسار القرار: إن كان تفعيل العنصر يغيّر عنوان URL أو ينقل إلى مكان آخر، فاستخدم عنصر a مع href؛ وإن كان يغيّر شيئًا في الصفحة الحالية، فاستخدم عنصر button؛ ولا تستخدم أبدًا div أو span عليه معالج نقر.</title>\n  <rect class=\"d-box-primary\" x=\"200\" y=\"15\" width=\"280\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"340\" y=\"40\" text-anchor=\"middle\">هل يأخذ المستخدم</text>\n  <text class=\"d-label-strong\" x=\"340\" y=\"60\" text-anchor=\"middle\">إلى URL أو مكان جديد؟</text>\n  <path class=\"d-arrow\" d=\"M260 71 L150 130\" marker-end=\"url(#arrow-l-a11y-2-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 71 L530 130\" marker-end=\"url(#arrow-l-a11y-2-2-1)\"/>\n  <text class=\"d-label-muted\" x=\"180\" y=\"100\" text-anchor=\"middle\">نعم</text>\n  <text class=\"d-label-muted\" x=\"500\" y=\"100\" text-anchor=\"middle\">لا، يفعل شيئًا هنا</text>\n  <rect class=\"d-box-success\" x=\"40\" y=\"135\" width=\"220\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"150\" y=\"168\" text-anchor=\"middle\">&lt;a href=\"…\"&gt;</text>\n  <rect class=\"d-box-success\" x=\"420\" y=\"135\" width=\"220\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"530\" y=\"168\" text-anchor=\"middle\">&lt;button&gt;</text>\n  <text class=\"d-label-muted\" x=\"340\" y=\"220\" text-anchor=\"middle\">أبدًا: div أو span عليه onclick</text>\n</svg><figcaption>اختيار العنصر الصحيح</figcaption></figure>\n<p>التنسيق لا علاقة له بالاختيار. يمكن أن يبدو الرابط كزر دعوة كبير، ويمكن أن يبدو الزر كرابط نصي؛ CSS يتولّى المظهر، والعنصر يتولّى السلوك.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ما الذي ينقص الـ div القابل للنقر</h2>\n<p>لتفهم لماذا يتفوّق العنصر الأصلي، عُدّ ما ينقص الـ <code dir=\"ltr\">div</code>. هذا الحدّ الأدنى لإعادة بناء زر يدويًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"btn\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> role</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tabindex</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"0\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Add to basket&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> fake</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">querySelector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'.btn'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">fake.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addEventListener</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'click'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, addToBasket);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">fake.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addEventListener</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'keydown'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (event.key </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Enter'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addToBasket</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (event.key </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ' '</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) event.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">preventDefault</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// stop the page scrolling</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">fake.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addEventListener</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'keyup'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (event.key </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ' '</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addToBasket</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// native buttons fire on Space release</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p>هذا دور، وتركيز، وEnter، وSpace، ومنع للتمرير، ومع ذلك ما زالت تنقصه حالة التعطيل، وإرسال النموذج، والطريقة التي تنسّق بها سمات التباين في Windows (وضع forced colors) الأزرار. قارن ذلك بـ:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"btn\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addToBasket</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">()\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Add to basket&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>عنصر واحد، وكل سلوك صحيح في كل متصفّح وكل قارئ شاشة. هذا هو النمط الذي ستراه على طول الدورة: <strong>HTML الأصلي أولًا</strong>، لأن المتصفّح أنجز العمل الصعب سلفًا.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>رابط الفعل href=“#”</span></p>\n<p>العنصر <code dir=\"ltr\">&lt;a href=&quot;#&quot; onclick=&quot;removeItem()&quot;&gt;Remove&lt;/a&gt;</code> قابل للتركيز ويعمل مع Enter، فيبدو كأنه أُصلح. لكنه يُعلَن على أنه رابط، ولا يستجيب لمفتاح Space، وإن فشل السكربت قفز إلى أعلى الصفحة وأضاف مدخلًا في سجلّ التصفّح. والعنصر <code dir=\"ltr\">&lt;a&gt;</code> بلا أي <code dir=\"ltr\">href</code> أسوأ: فهو ليس قابلًا للتركيز حتى. إن كان يفعل شيئًا، فاجعله زرًّا.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">الأزرار في النماذج، والأزرار المعطّلة</h2>\n<p>داخل <code dir=\"ltr\">&lt;form&gt;</code>، يكون <code dir=\"ltr\">&lt;button&gt;</code> افتراضيًا <code dir=\"ltr\">type=&quot;submit&quot;</code>. في صفحة الدفع في Ticketly زر “Apply promo code” كان يُرسل النموذج كله مرة بعد مرة. والإصلاح خاصية واحدة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">applyPromo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">()\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Apply promo code&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>الخاصية <code dir=\"ltr\">disabled</code> تُخرج الزر من ترتيب التنقّل بمفتاح Tab، فيتخطّاه مستخدمو لوحة المفاتيح وهم يتنقّلون في النموذج، ولا يُخبَر أحد لماذا هو غير متاح. صحيح أن قارئ الشاشة في وضع التصفّح (browse mode) يجده ويُعلنه “unavailable”، لكن دون أي سبب. لزر “Pay now” الذي ينتظر بطاقة صالحة، فكّر في إبقائه مفعّلًا وشرح المشكلة حين يُضغط، وهو ما ستبنيه في درس <a href=\"/ar/lesson/web-accessibility/l-a11y-2-3\">نماذج سهلة الوصول</a>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">نص مفهوم وحده</h2>\n<p>كثيرًا ما يستدعي مستخدمو قارئ الشاشة قائمة بكل الروابط أو الأزرار في الصفحة، ومستخدمو التحكّم الصوتي ينطقون النص المرئي لينقروه. “Read more” و&quot;Click here&quot; و&quot;Details&quot; بلا معنى في تلك القائمة. يطلب معيار WCAG ‏2.4.4 أن يكون غرض الرابط واضحًا من نصه، أو من نصه مع الجملة أو عنصر القائمة المحيط به. واطمح إلى ما هو أعلى من الحدّ الأدنى: اكتب “Friday line-up and set times” لا “Read more”. وإن أصرّ التصميم على نص مرئي قصير، فستتعلّم في درس <a href=\"/ar/lesson/web-accessibility/l-a11y-4-3\">الأسماء والأوصاف المتاحة</a> كيف توسّع الاسم دون أن تكسر التحكّم الصوتي.</p>\n<p>وعادة أخرى مع الروابط: إن كان الرابط يفتح تبويبًا جديدًا أو يُنزّل ملفًا، فقل ذلك في نصه، مثل “Venue guide (PDF, 2 MB)” أو “Seating plan (opens in a new tab)”. التبويب الجديد غير المتوقّع يكسر زر الرجوع لمستخدمي قارئ الشاشة وبرامج التكبير، الذين كثيرًا ما لا يلاحظون أن النافذة تغيّرت فيتساءلون لماذا لا يستطيعون العودة.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>اختبار لوحة المفاتيح السريع</span></p>\n<p>افصل الفأرة (أو تجاهلها) وتنقّل في Ticketly بمفتاح Tab. كل عنصر تستطيع نقره يجب أن يتلقّى التركيز، وأن يُظهر موضع التركيز، وأن يستجيب لـ Enter، ولـ Space أيضًا إن كان زرًّا. وكل ما يفشل يكون في الغالب div أو span أو رابطًا بلا href.</p>\n</aside>\n<p>الأزرار والروابط الحقيقية تُصلح عناصر التحكّم التي تفعل شيئًا. وبعدها تأتي العناصر التي تجمع المعلومات، والمكان الذي تخسر فيه معظم المواقع غير سهلة الوصول عملاءها: النماذج.</p>\n","toc":[{"id":"sec-1","text":"الروابط تنقلك، والأزرار تفعل","level":2},{"id":"sec-2","text":"ما الذي ينقص الـ div القابل للنقر","level":2},{"id":"sec-3","text":"الأزرار في النماذج، والأزرار المعطّلة","level":2},{"id":"sec-4","text":"نص مفهوم وحده","level":2}],"takeaways":["الرابط يأخذ المستخدم إلى مكان ما (عنوان URL جديد أو جزء من الصفحة)؛ أما الزر فيفعل شيئًا في الصفحة الحالية.","الأزرار الأصلية قابلة للتركيز، وتُعلَن على أنها أزرار، وتُفعَّل بمفتاحي Enter وSpace، وكل ذلك مجانًا.","الـ div الذي عليه معالج نقر ليس له دور ولا تركيز ولا دعم للوحة المفاتيح؛ وإعادة بناء ذلك يدويًا تتطلّب أربعة إصلاحات منفصلة ويسهل أن تخطئ فيها.","داخل النماذج، أعطِ الأزرار التي لا تُرسل النموذج type=“button”، واكتب نصوصًا للروابط والأزرار تبقى مفهومة خارج سياقها."],"further":[{"title":"MDN: The Button element","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button"},{"title":"MDN: The Anchor element","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a"},{"title":"W3C: Understanding 2.4.4 Link Purpose (In Context)","url":"https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html"}],"quiz":[{"q":"<p>أيّ هذه العناصر يجب أن يكون رابطًا لا زرًّا؟</p>","options":[{"text":"“Add to basket”، الذي يحدّث عدّادًا في الصفحة","why":"هذا يغيّر الحالة في الصفحة الحالية دون انتقال، فهو زر."},{"text":"“Show more dates”، الذي يوسّع قائمة مخفية","why":"توسيع المحتوى في مكانه فعلٌ، وهذا يجعله زرًّا (زر disclosure، وسنتناوله لاحقًا في الدورة)."},{"text":"“Friday line-up”، الذي ينتقل إلى <code dir=\"ltr\">/events/friday</code>","why":"صحيح. إنه يأخذ المستخدم إلى عنوان URL جديد، فيجب أن يكون عنصر a له href. وبذلك يستطيع المستخدمون فتحه في تبويب جديد أو نسخ عنوانه."}],"answer":2},{"q":"<p>«أصلح» أحد المطوّرين هذا الزر المزيّف. ما الذي ما زال ناقصًا؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> role</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tabindex</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"0\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">addToBasket</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">()\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Add to basket&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"لا شيء؛ الـ role والـ tabindex يجعلانه مكافئًا للزر","why":"الـ role يغيّر ما يُعلَن، والـ tabindex يضيف التركيز، لكن أيًّا منهما لا يجعل العنصر يستجيب للوحة المفاتيح."},{"text":"التفعيل بلوحة المفاتيح؛ ضغط Enter أو Space لا يفعل شيئًا لأن الـ div لا يطلق حدث click عند ضغط المفاتيح","why":"صحيح. ستحتاج إلى معالج keydown لمفتاحي Enter وSpace (وإلى منع Space من تمرير الصفحة). الزر الحقيقي يفعل هذا أصلًا."},{"text":"خاصية aria-label، لأن قارئات الشاشة لا تستطيع قراءة النص داخل div","why":"النص “Add to basket” يعطيه اسمًا متاحًا أصلًا. وإضافة aria-label لن تفعل في أحسن الأحوال سوى تكراره."}],"answer":1},{"q":"<p>داخل نموذج الدفع في Ticketly، يُرسل زر «طبّق رمز الخصم» النموذج كله ويعرض «رقم البطاقة مطلوب». لماذا؟</p>","options":[{"text":"الزر داخل النموذج يكون افتراضيًا type=“submit”","why":"صحيح. أضف <code dir=\"ltr\">type=&quot;button&quot;</code> إلى الأزرار التي تشغّل JavaScript بدلًا من إرسال النموذج."},{"text":"الزر يحتاج إلى خاصية aria-controls تشير إلى الحقل","why":"الخاصية aria-controls تصف علاقة للتقنيات المساعدة فقط؛ ولا تغيّر أبدًا ما يفعله الزر."},{"text":"لا يمكن استخدام الأزرار داخل النماذج، فيجب أن يكون رابطًا","why":"الأزرار هي عناصر التحكّم الطبيعية داخل النماذج. والرابط سيكون الدور الخاطئ لفعلٍ ما."}],"answer":0},{"q":"<p>يستدعي مستخدم قارئ شاشة قائمة الروابط في Ticketly فيسمع “Read more, Read more, Read more”. ما أفضل إصلاح؟</p>","options":[{"text":"أضف <code dir=\"ltr\">title=&quot;Read more about Friday&quot;</code> إلى كل رابط","why":"لا تُعلَن الـ title بشكل موثوق، وهي غير مرئية لمستخدمي لوحة المفاتيح واللمس، فهي إصلاح ضعيف."},{"text":"احذف الروابط واعتمد على العناوين بدلًا منها","why":"الروابط ما زالت مفيدة. المشكلة أن نصّها لا يحدّد وجهتها."},{"text":"اجعل نص الرابط يقول إلى أين يذهب، مثل “Friday line-up and set times”","why":"صحيح. نص الرابط الوصفي يعمل في قائمة الروابط، ولمستخدمي التحكّم الصوتي، وللمبصرين الذين يتصفّحون الصفحة بسرعة."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<p>Basket: <span id=\"basket-count\">0</span> tickets</p>\n<article class=\"pass\">\n  <h2>Friday pass</h2>\n  <p>SAR 180, standing.</p>\n  <div class=\"btn\" onclick=\"addToBasket()\">Add to basket</div>\n  <a href=\"#\" class=\"text-btn\" onclick=\"removeFromBasket(); return false;\">Remove one</a>\n  <span class=\"link\" onclick=\"location.hash = '#lineup'\">See the Friday line-up</span>\n</article>\n<h2 id=\"lineup\">Friday line-up</h2>\n<p>Nour Quartet, The Saltmarsh Band.</p>","css":".pass { border: 1px solid #c9c4dc; border-radius: 12px; padding: 12px 16px; display: grid; gap: 8px; justify-items: start; }\n.pass h2 { margin: 0; font-size: 1.2rem; }\n.pass p { margin: 0; }\n.btn { background: #5b3cc4; color: #fff; border: 0; border-radius: 8px; padding: 8px 16px; font: inherit; cursor: pointer; }\n.text-btn { background: none; border: 0; padding: 0; font: inherit; color: #5b3cc4; text-decoration: underline; cursor: pointer; }\n.link { color: #5b3cc4; text-decoration: underline; cursor: pointer; }\n","js":"let count = 0;\nfunction addToBasket() {\n  count += 1;\n  document.querySelector('#basket-count').textContent = String(count);\n}\nfunction removeFromBasket() {\n  count = Math.max(0, count - 1);\n  document.querySelector('#basket-count').textContent = String(count);\n}\n"},"solution":{"html":"<p>Basket: <span id=\"basket-count\">0</span> tickets</p>\n<article class=\"pass\">\n  <h2>Friday pass</h2>\n  <p>SAR 180, standing.</p>\n  <button type=\"button\" class=\"btn\" onclick=\"addToBasket()\">Add to basket</button>\n  <button type=\"button\" class=\"text-btn\" onclick=\"removeFromBasket()\">Remove one</button>\n  <a href=\"#lineup\" class=\"link\">See the Friday line-up</a>\n</article>\n<h2 id=\"lineup\">Friday line-up</h2>\n<p>Nour Quartet, The Saltmarsh Band.</p>","css":".pass { border: 1px solid #c9c4dc; border-radius: 12px; padding: 12px 16px; display: grid; gap: 8px; justify-items: start; }\n.pass h2 { margin: 0; font-size: 1.2rem; }\n.pass p { margin: 0; }\n.btn { background: #5b3cc4; color: #fff; border: 0; border-radius: 8px; padding: 8px 16px; font: inherit; cursor: pointer; }\n.text-btn { background: none; border: 0; padding: 0; font: inherit; color: #5b3cc4; text-decoration: underline; cursor: pointer; }\n.link { color: #5b3cc4; text-decoration: underline; cursor: pointer; }\n","js":"let count = 0;\nfunction addToBasket() {\n  count += 1;\n  document.querySelector('#basket-count').textContent = String(count);\n}\nfunction removeFromBasket() {\n  count = Math.max(0, count - 1);\n  document.querySelector('#basket-count').textContent = String(count);\n}\n"},"checks":[{"id":"add-is-button","test":"const find = (t) => $$('body *').filter((el) => el.children.length === 0 || el.matches('button, a')).find((el) => el.textContent.trim() === t); const el = find('Add to basket'); return !!el && !!el.closest('button');"},{"id":"add-works","test":"const b = $$('button').find((x) => x.textContent.trim() === 'Add to basket'); if (!b) return false; const before = Number(text('#basket-count')); await click(b); return Number(text('#basket-count')) === before + 1;"},{"id":"remove-is-button","test":"const find = (t) => $$('body *').filter((el) => el.children.length === 0 || el.matches('button, a')).find((el) => el.textContent.trim() === t); const el = find('Remove one'); const b = el && el.closest('button'); if (!b) return false; const before = Number(text('#basket-count')); await click(b); return Number(text('#basket-count')) === Math.max(0, before - 1);"},{"id":"lineup-is-link","test":"const find = (t) => $$('body *').filter((el) => el.children.length === 0 || el.matches('button, a')).find((el) => el.textContent.trim() === t); const el = find('See the Friday line-up'); const a = el && el.closest('a[href]'); return !!a && a.getAttribute('href').endsWith('#lineup');"},{"id":"no-fakes","test":"return $$('div[onclick], span[onclick], a[href=\"#\"], a:not([href])').length === 0;"}],"title":"استبدل عناصر التحكّم المزيّفة في Ticketly","prompt":"<p>في بطاقة تذكرة الجمعة ثلاثة عناصر تحكّم، وليس أيٌّ منها العنصر الصحيح:</p>\n<ul>\n<li><strong>Add to basket</strong> عنصر <code dir=\"ltr\">div</code> عليه <code dir=\"ltr\">onclick</code>. يشغّل <code dir=\"ltr\">addToBasket()</code>.</li>\n<li><strong>Remove one</strong> عنصر <code dir=\"ltr\">&lt;a href=&quot;#&quot;&gt;</code> يشغّل <code dir=\"ltr\">removeFromBasket()</code>.</li>\n<li><strong>See the Friday line-up</strong> عنصر <code dir=\"ltr\">span</code> يغيّر الـ hash في عنوان URL إلى <code dir=\"ltr\">#lineup</code>.</li>\n</ul>\n<p>أعد بناء كلٍّ منها بالعنصر الأصلي الصحيح، مع إبقاء النص المرئي كما هو تمامًا. يجب أن يظلّ الفعلان يحدّثان عدد التذاكر في السلة. ويمكنك الإبقاء على أسماء الكلاسات ليبقى التنسيق كما هو.</p>\n","hints":["اسأل عن كل عنصر: هل يأخذ المستخدم إلى مكان ما، أم يفعل شيئًا هنا؟","فعلا السلة زرّان. أعطِهما type=“button”.","الانتقال إلى ‎#lineup هو ما يفعله رابط href=“#lineup” وحده؛ ولا يحتاج إلى JavaScript."],"explanation":"<p>Add to basket وRemove one يغيّران الحالة في الصفحة، فهما عنصرا <code dir=\"ltr\">&lt;button type=&quot;button&quot;&gt;</code>: قابلان للتركيز، ويُعلَنان على أنهما أزرار، ويُفعَّلان بـ Enter وSpace دون أي كود إضافي. أما See the Friday line-up فينقل المستخدم إلى مكان آخر، فهو <code dir=\"ltr\">&lt;a href=&quot;#lineup&quot;&gt;</code>، ويعمل أيضًا دون JavaScript، ويُظهر وجهته عند التمرير فوقه، ويمكن نسخه. لاحظ أن الحلّ فيه كود أقل من نقطة البداية: اختفى <code dir=\"ltr\">return false</code> واختفى السكربت الذي يضبط الـ hash، لأن العناصر الأصلية تتصرّف بشكل صحيح أصلًا.</p>\n","checkLabels":{"add-is-button":"“Add to basket” زر حقيقي","add-works":"نقر “Add to basket” يضيف تذكرة واحدة إلى عدد السلة","remove-is-button":"“Remove one” زر حقيقي يُنقص العدد","lineup-is-link":"“See the Friday line-up” رابط ينتهي الـ href فيه بـ ‎#lineup","no-fakes":"لم يبقَ أي معالج نقر على div أو span، ولا روابط بـ href=“#” أو بلا href"}},"words":644,"readMinutes":4}