{"id":"l-a11y-2-3","courseId":"web-accessibility","lang":"ar","kind":"lesson","summary":"ابنِ نموذج دفع لكل حقل فيه label، وخياراته المترابطة مجمّعة، ويستطيع المتصفّح تعبئته تلقائيًا، وأخطاؤه تُعلَن ويمكن إصلاحها دون الحاجة إلى البصر.","html":"<p>يخسر Ticketly ربع عملائه في صفحة الدفع، والتحليلات لا تستطيع أن تقول لماذا. جرّب النموذج بقارئ شاشة فيتّضح السبب: الحقول تُعلن “edit text, edit text, edit text”، وأزرار نوع التذكرة تُعلن “Standing, radio button” بلا سؤال، وحين يفشل الدفع يظهر إطار أحمر لا يدركه من لا يرى الشاشة. النماذج هي المكان الذي تتحوّل فيه إمكانية الوصول مباشرة إلى مال.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">لكل حقل label</h2>\n<p>عنصر <code dir=\"ltr\">&lt;label&gt;</code> المرتبط بحقل إدخال يفعل ثلاثة أشياء: يمنح الحقل اسمه المتاح، ويُظهر للمبصرين ما يجب أن يكتبوه حتى بعد أن يبدؤوا الكتابة، ويجعل نص الـ label قابلًا للنقر، فيكبر الهدف.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ticketly/checkout.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\">label</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"full-name\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Full name&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"full-name\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fullName\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> autocomplete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"name\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  Email</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</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\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> autocomplete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> required</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>الطريقتان تعملان: <code dir=\"ltr\">for</code> تشير إلى <code dir=\"ltr\">id</code>، أو حقل الإدخال متداخل داخل الـ label. ويمكنك التأكّد من الربط في الـ console: يجب أن تكون قيمة <code dir=\"ltr\">document.querySelector('#full-name').labels.length</code> هي 1.</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>الـ placeholder بدلًا من الـ label</span></p>\n<p>يبدو <code dir=\"ltr\">placeholder=&quot;Email&quot;</code> أنيقًا، لكنه يختفي لحظة أن يكتب المستخدم، فمن يتشتّت انتباهه في منتصف الحقل يكون قد فقد السؤال. ولونه الرمادي الافتراضي يفشل عادةً في التباين، ولا تعامله كل التقنيات المساعدة كاسم. استخدم label مرئيًا؛ واترك الـ placeholder لأمثلة الصيغ، أو استغنِ عنه.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">اجمع الخيارات المترابطة</h2>\n<p>لأزرار الـ radio ومربّعات الاختيار طبقتان من المعنى: كل خيار وحده، والسؤال الذي تجيب عنه معًا. يحمل <code dir=\"ltr\">&lt;fieldset&gt;</code> و<code dir=\"ltr\">&lt;legend&gt;</code> السؤال:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"1\"><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\">fieldset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">legend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Ticket type&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">legend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</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\">\"radio\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ticket\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"standing\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> Standing&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</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\">\"radio\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ticket\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"seated\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> Seated&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</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\">\"radio\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ticket\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"vip\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> VIP lounge&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">fieldset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>حين يدخل التركيز المجموعة، يقرأ NVDA: “Ticket type, grouping, Standing, radio button, not checked, 1 of 3”. استخدم النمط نفسه لكتلة عنوان الفوترة أو لمجموعة مربّعات «أبلغني عبر».</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">ساعد الناس والمتصفّحات على التعبئة</h2>\n<p>خاصيتان توفّران كثيرًا من الكتابة، وهذا أهمّ ما يكون لذوي الإعاقات الحركية أو صعوبات الذاكرة.</p>\n<p>الـ <code dir=\"ltr\">type</code> الصحيح يُظهر لوحة المفاتيح المناسبة على الهواتف ويوفّر تحقّقًا أساسيًا: <code dir=\"ltr\">type=&quot;email&quot;</code>، و<code dir=\"ltr\">type=&quot;tel&quot;</code>، و<code dir=\"ltr\">inputmode=&quot;numeric&quot;</code> لرقم البطاقة.</p>\n<p>والخاصية <code dir=\"ltr\">autocomplete</code> تعلن الغرض من الحقل، فيستطيع المتصفّح تعبئته من البيانات المحفوظة. يشترطها معيار WCAG ‏1.3.5 Identify Input Purpose ‏(AA) للحقول التي تخصّ المستخدم. من القيم الشائعة: <code dir=\"ltr\">name</code>، <code dir=\"ltr\">given-name</code>، <code dir=\"ltr\">family-name</code>، <code dir=\"ltr\">email</code>، <code dir=\"ltr\">tel</code>، <code dir=\"ltr\">street-address</code>، <code dir=\"ltr\">postal-code</code>، <code dir=\"ltr\">country</code>، <code dir=\"ltr\">cc-name</code>، <code dir=\"ltr\">cc-number</code>، <code dir=\"ltr\">cc-exp</code>، <code dir=\"ltr\">cc-csc</code>، <code dir=\"ltr\">username</code>، <code dir=\"ltr\">current-password</code>، <code dir=\"ltr\">new-password</code>.</p>\n<p>علِّم الحقول المطلوبة بخاصية <code dir=\"ltr\">required</code>، التي تُعلنها قارئات الشاشة، وبنص مرئي مثل “(required)”، لا بنجمة فقط. ولا تسأل مرتين: إن كتب المستخدم بريده في الخطوة 1، فاملأه مسبقًا في الخطوة 3 (3.3.7 Redundant Entry).</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">أخطاء يستطيع الناس العثور عليها وإصلاحها</h2>\n<p>هنا تفشل معظم النماذج. للخطأ الجيد أربعة أجزاء، ويوضّح الرسم كيف تترابط:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-a11y-2-3-t1\"><defs><marker id=\"arrow-l-a11y-2-3-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-3-t1\">يمنح عنصر label حقلَ الإدخال اسمه عبر الخاصية for. ويحمل الحقل aria-invalid بقيمة true، وتشير aria-describedby إلى تلميح ورسالة خطأ يصبحان وصفه. وينتقل التركيز إلى الحقل بعد إرسال فاشل.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"20\" width=\"170\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"105\" y=\"50\" text-anchor=\"middle\">&lt;label for&gt;</text>\n  <rect class=\"d-box-primary\" x=\"250\" y=\"80\" width=\"190\" height=\"70\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"345\" y=\"110\" text-anchor=\"middle\">&lt;input&gt;</text>\n  <text class=\"d-code\" x=\"345\" y=\"134\" text-anchor=\"middle\">aria-invalid=\"true\"</text>\n  <rect class=\"d-box\" x=\"500\" y=\"20\" width=\"160\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"580\" y=\"50\" text-anchor=\"middle\">نص التلميح</text>\n  <rect class=\"d-box-warn\" x=\"500\" y=\"150\" width=\"160\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"580\" y=\"180\" text-anchor=\"middle\">رسالة الخطأ</text>\n  <path class=\"d-arrow\" d=\"M190 45 L250 95\" marker-end=\"url(#arrow-l-a11y-2-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"190\" y=\"90\">الاسم</text>\n  <path class=\"d-arrow\" d=\"M500 45 L440 95\" marker-end=\"url(#arrow-l-a11y-2-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M500 175 L440 135\" marker-end=\"url(#arrow-l-a11y-2-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"455\" y=\"80\">الوصف</text>\n  <text class=\"d-label-muted\" x=\"345\" y=\"200\" text-anchor=\"middle\">التركيز يصل هنا عند الخطأ</text>\n</svg><figcaption>أجزاء الحقل سهل الوصول</figcaption></figure>\n<ol>\n<li><strong>قل أيّ حقل</strong> خاطئ، بالنص (3.3.1 Error Identification).</li>\n<li><strong>قل كيف يُصلَح</strong> (3.3.3 Error Suggestion): “Enter an email address like name@example.com”، لا “Invalid input”.</li>\n<li><strong>اربطه برمجيًا</strong>: <code dir=\"ltr\">aria-invalid=&quot;true&quot;</code> على الحقل و<code dir=\"ltr\">aria-describedby</code> تشير إلى الرسالة.</li>\n<li><strong>انقل التركيز</strong> إلى أول حقل غير صالح (أو إلى ملخّص أخطاء في الأعلى في النماذج الطويلة) ليصل المستخدم إلى المشكلة مباشرة.</li>\n</ol>\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\">label</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Email&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</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\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> autocomplete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> required</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email-error\"</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\">\"error\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</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=\"3\"><div class=\"code-head\"><span class=\"code-title\">ticketly/checkout.js</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\"> form</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\">'#checkout'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> email</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> form.</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\">'#email'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> error</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> form.</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\">'#email-error'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">form.</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\">'submit'</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\"> (email.value.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">includes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'@'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    email.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">removeAttribute</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'aria-invalid'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    email.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">removeAttribute</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'aria-describedby'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    error.hidden </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</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\">  event.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">preventDefault</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  error.textContent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Enter an email address like name@example.com'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  error.hidden </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  email.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setAttribute</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'aria-invalid'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'true'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  email.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setAttribute</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'aria-describedby'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'email-error'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  email.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">focus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p>يستخدم النموذج <code dir=\"ltr\">novalidate</code> في كود Ticketly ‏(<code dir=\"ltr\">&lt;form id=&quot;checkout&quot; novalidate&gt;</code>) لتحلّ هذه الرسالة المخصّصة محلّ الفقاعة المدمجة في المتصفّح، التي تختلف من متصفّح لآخر وتختفي بسرعة. هذا اختيار شائع؛ والإبقاء على التحقّق الأصلي مقبول أيضًا إن كانت رسائله ترضيك.</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>العنصر الذي تشير إليه <code dir=\"ltr\">aria-describedby</code> يُقرأ حتى وهو مخفي. إن تركت الإشارة في مكانها بعد أن يُصلح المستخدم الحقل، فسيظلّ يسمع خطأً قديمًا. احذف الخاصية (أو أفرغ الرسالة) حين يصبح الحقل صالحًا، كما يفعل الكود أعلاه.</p>\n</aside>\n<p>وفي النموذج الطويل الذي فيه عدة أخطاء، أضف <strong>ملخّص أخطاء</strong> في الأعلى: عنوانًا مثل “There are 2 problems”، تتبعه قائمة روابط، كلٌّ منها يشير إلى الحقل الذي يصفه (<code dir=\"ltr\">&lt;a href=&quot;#email&quot;&gt;Enter an email address&lt;/a&gt;</code>). وانقل التركيز إلى الملخّص بعد إرسال فاشل. يسمع المستخدمون عدد المشكلات، ويأخذهم كل رابط مباشرة إلى حقل.</p>\n<p>تحقّق من صحة المدخلات عند الإرسال أو حين يغادر المستخدم الحقل، لا مع كل ضغطة مفتاح. الصراخ بـ «بريد غير صالح» بينما لا يزال الشخص يكتب الحروف الأولى ضجيج للجميع، وسيل من الإعلانات لمستخدمي قارئ الشاشة.</p>\n<p>صارت نماذج Ticketly تتكلّم بوضوح. والجزء الأخير من القسم 2 يتناول محتوى ليس نصًا على الإطلاق: الصور والأيقونات وجداول البيانات في صفحة الأسعار.</p>\n","toc":[{"id":"sec-1","text":"لكل حقل label","level":2},{"id":"sec-2","text":"اجمع الخيارات المترابطة","level":2},{"id":"sec-3","text":"ساعد الناس والمتصفّحات على التعبئة","level":2},{"id":"sec-4","text":"أخطاء يستطيع الناس العثور عليها وإصلاحها","level":2}],"takeaways":["كل حقل إدخال يحتاج إلى label برمجي؛ والـ placeholder يختفي عند الكتابة وليس اسمًا يمكن الاعتماد عليه.","اجمع أزرار الاختيار (radio) ومربّعات الاختيار (checkbox) في fieldset يطرح الـ legend فيه السؤال.","قيم autocomplete مثل name وemail وcc-number تتيح للمتصفّحات تعبئة الحقول وتحقّق معيار 1.3.5 Identify Input Purpose.","عند الخطأ، اضبط aria-invalid، واربط الرسالة بـ aria-describedby، واشرح كيف يُصلَح الخطأ، وانقل التركيز إلى أول حقل غير صالح."],"further":[{"title":"W3C WAI: Forms Tutorial","url":"https://www.w3.org/WAI/tutorials/forms/"},{"title":"MDN: HTML autocomplete attribute","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete"},{"title":"MDN: The Fieldset element","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset"}],"quiz":[{"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\">input</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\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"email\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> placeholder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Email\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"“Email, edit text” في معظم المتصفّحات، إلى أن يكتب المستخدم، وعندها تختفي الإشارة من الشاشة","why":"صحيح. تلجأ المتصفّحات إلى الـ placeholder كاسم احتياطي، لكن ما إن يكتب المستخدم حتى يختفي التلميح المرئي، وبعض الأدوات لا تستخدمه أصلًا. والـ label الحقيقي يحلّ المشكلتين."},{"text":"“Edit text” بلا اسم في كل متصفّح وكل قارئ شاشة","why":"معظم المتصفّحات تستخدم الـ placeholder فعلًا كاسم أخير. المشكلة أنه هشّ ويختفي بصريًا، لا أنه صامت دائمًا."},{"text":"“Email, required, edit text”","why":"لا شيء يعلّم الحقل بأنه مطلوب. هذا يحتاج إلى خاصية required."}],"answer":0},{"q":"<p>يسأل Ticketly عن «نوع التذكرة» بثلاثة أزرار radio. كيف يجب أن يُكتب السؤال؟</p>","options":[{"text":"فقرة فيها نص السؤال فوق أزرار الـ radio","why":"الفقرة غير مرتبطة برمجيًا بأزرار الـ radio، فالدخول إلى المجموعة لا يُعلن السؤال."},{"text":"عنصر fieldset يضمّ أزرار الـ radio، مع legend يقول “Ticket type”","why":"صحيح. تُعلن قارئات الشاشة الـ legend حين يدخل التركيز المجموعة، فيسمع المستخدمون “Ticket type, grouping, Standing, radio button, 1 of 3”."},{"text":"خاصية aria-label=“Ticket type” على كل زر radio","why":"هذا يحلّ محلّ اسم كل خيار، فيُعلَن كل زر radio على أنه “Ticket type” ولا يستطيع المستخدمون التمييز بينها."}],"answer":1},{"q":"<p>بعد محاولة إرسال فاشلة، أيّ تغيير يفيد مستخدم قارئ الشاشة أكثر من غيره؟</p>","options":[{"text":"تحويل إطار الحقل غير الصالح إلى الأحمر","why":"اللون وحده لا يدركه مستخدمو قارئ الشاشة ولا كثير من المصابين بعمى الألوان، وهو يخالف معيار 1.4.1 Use of Color."},{"text":"عرض إشعار منبثق «في النموذج أخطاء» لثلاث ثوانٍ","why":"الإشعار القصير البعيد عن الحقل يسهل أن يفوت، ولا يقول أيّ حقل خاطئ ولا كيف يُصلَح."},{"text":"ضبط aria-invalid على الحقل، وربط الرسالة بـ aria-describedby، ونقل التركيز إلى الحقل","why":"صحيح. يصل التركيز إلى المشكلة، ويقرأ قارئ الشاشة “Email, invalid entry, Enter an email address like name@example.com”."}],"answer":2},{"q":"<p>أيّ قيمة autocomplete صحيحة لاسم حامل البطاقة في خطوة الدفع في Ticketly؟</p>","options":[{"text":"<code dir=\"ltr\">autocomplete=&quot;name&quot;</code>","why":"القيمة name مخصّصة لاسم المستخدم الكامل. قد تُعبّأ بشكل صحيح بالصدفة، لكن القيمة الخاصة بالاسم على البطاقة أكثر تحديدًا."},{"text":"<code dir=\"ltr\">autocomplete=&quot;cc-name&quot;</code>","why":"صحيح. cc-name هي القيمة الخاصة بالاسم المطبوع على بطاقة الدفع، إلى جانب cc-number وcc-exp وcc-csc."},{"text":"<code dir=\"ltr\">autocomplete=&quot;on&quot;</code>","why":"القيمة on تسمح للمتصفّح بالتخمين لكنها لا تعلن أي غرض، فلا تحقّق معيار 1.3.5 Identify Input Purpose."}],"answer":1}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<form id=\"checkout\" novalidate>\n  <input id=\"full-name\" name=\"fullName\" placeholder=\"Full name\">\n  <input id=\"email\" name=\"email\" type=\"text\" placeholder=\"Email\">\n  <p id=\"email-error\" class=\"error\" hidden></p>\n\n  <p class=\"question\">Ticket type</p>\n  <input type=\"radio\" name=\"ticket\" value=\"standing\" id=\"t-standing\"> Standing\n  <input type=\"radio\" name=\"ticket\" value=\"seated\" id=\"t-seated\"> Seated\n\n  <button id=\"pay\">Pay now</button>\n</form>","css":"form { display: grid; gap: 6px; max-width: 320px; }\ninput:not([type=\"radio\"]) { font: inherit; padding: 6px 8px; border: 1px solid #77708f; border-radius: 6px; }\nfieldset { border: 1px solid #c9c4dc; border-radius: 8px; padding: 8px 12px; }\n.error { color: #b3261e; margin: 0; }\n[aria-invalid=\"true\"] { border-color: #b3261e; border-width: 2px; }\nbutton { justify-self: start; font: inherit; padding: 8px 16px; border-radius: 8px; border: 0; background: #5b3cc4; color: #fff; }\n","js":"const email = document.querySelector('#email');\nconst error = document.querySelector('#email-error');\n\n// The playground blocks real form submission, so validation runs on the\n// submit button's click. That click also fires when Enter is pressed in a field.\ndocument.querySelector('#pay').addEventListener('click', (event) => {\n  event.preventDefault();\n  if (email.value.includes('@')) {\n    error.hidden = true;\n    return;\n  }\n  error.textContent = 'Enter an email address like name@example.com';\n  error.hidden = false;\n});\n"},"solution":{"html":"<form id=\"checkout\" novalidate>\n  <label for=\"full-name\">Full name</label>\n  <input id=\"full-name\" name=\"fullName\" autocomplete=\"name\">\n  <label for=\"email\">Email</label>\n  <input id=\"email\" name=\"email\" type=\"email\" autocomplete=\"email\">\n  <p id=\"email-error\" class=\"error\" hidden></p>\n\n  <fieldset>\n    <legend>Ticket type</legend>\n    <label><input type=\"radio\" name=\"ticket\" value=\"standing\" id=\"t-standing\"> Standing</label>\n    <label><input type=\"radio\" name=\"ticket\" value=\"seated\" id=\"t-seated\"> Seated</label>\n  </fieldset>\n\n  <button id=\"pay\">Pay now</button>\n</form>","css":"form { display: grid; gap: 6px; max-width: 320px; }\ninput:not([type=\"radio\"]) { font: inherit; padding: 6px 8px; border: 1px solid #77708f; border-radius: 6px; }\nfieldset { border: 1px solid #c9c4dc; border-radius: 8px; padding: 8px 12px; }\n.error { color: #b3261e; margin: 0; }\n[aria-invalid=\"true\"] { border-color: #b3261e; border-width: 2px; }\nbutton { justify-self: start; font: inherit; padding: 8px 16px; border-radius: 8px; border: 0; background: #5b3cc4; color: #fff; }\n","js":"const email = document.querySelector('#email');\nconst error = document.querySelector('#email-error');\n\n// The playground blocks real form submission, so validation runs on the\n// submit button's click. That click also fires when Enter is pressed in a field.\ndocument.querySelector('#pay').addEventListener('click', (event) => {\n  event.preventDefault();\n  if (email.value.includes('@')) {\n    error.hidden = true;\n    email.removeAttribute('aria-invalid');\n    email.removeAttribute('aria-describedby');\n    return;\n  }\n  error.textContent = 'Enter an email address like name@example.com';\n  error.hidden = false;\n  email.setAttribute('aria-invalid', 'true');\n  email.setAttribute('aria-describedby', 'email-error');\n  email.focus();\n});\n"},"checks":[{"id":"labels","test":"const lbl = (el) => [...(el.labels || [])].map((l) => l.textContent).join(' ').replace(/\\s+/g, ' ').trim(); const fields = $$('input'); return fields.length === 4 && fields.every((f) => lbl(f).length > 0) && /full name/i.test(lbl($('#full-name'))) && /email/i.test(lbl($('#email'))) && /standing/i.test(lbl($('#t-standing'))) && /seated/i.test(lbl($('#t-seated')));"},{"id":"fieldset","test":"const radios = $$('input[type=\"radio\"]'); const fs = radios[0] && radios[0].closest('fieldset'); if (!fs || !radios.every((r) => fs.contains(r))) return false; const lg = fs.querySelector('legend'); return !!lg && fs.firstElementChild === lg && /ticket type/i.test(lg.textContent);"},{"id":"autocomplete","test":"return $('#full-name').getAttribute('autocomplete') === 'name' && $('#email').getAttribute('autocomplete') === 'email' && $('#email').type === 'email';"},{"id":"error-linked","test":"const email = $('#email'); await input(email, 'samir.example.com'); await click('#pay'); if (email.getAttribute('aria-invalid') !== 'true') return false; const ids = (email.getAttribute('aria-describedby') || '').split(/\\s+/).filter(Boolean); return ids.some((id) => { const el = document.getElementById(id); return el && !el.hidden && /enter an email/i.test(el.textContent); });"},{"id":"error-focus","test":"return document.activeElement === $('#email');"},{"id":"error-clears","test":"const email = $('#email'); await input(email, 'samir@example.com'); await click('#pay'); if (email.getAttribute('aria-invalid') === 'true') return false; const ids = (email.getAttribute('aria-describedby') || '').split(/\\s+/).filter(Boolean); return !ids.some((id) => { const el = document.getElementById(id); return el && /enter an email/i.test(el.textContent) && !el.hidden; }) && $('#email-error').hidden;"}],"title":"أصلح نموذج الدفع في Ticketly","prompt":"<p>يعتمد نموذج الدفع على الـ placeholders، ويضع سؤالًا عائمًا فوق أزرار radio بلا labels، ويعرض خطأ البريد الإلكتروني بطريقة لا يلاحظها إلا المبصرون. أصلحه:</p>\n<ol>\n<li>أعطِ <code dir=\"ltr\">#full-name</code> و<code dir=\"ltr\">#email</code> وزرّي الـ radio عنصر <code dir=\"ltr\">&lt;label&gt;</code> حقيقيًا (“Full name”، “Email”، “Standing”، “Seated”).</li>\n<li>اجمع أزرار الـ radio في <code dir=\"ltr\">&lt;fieldset&gt;</code> يكون الـ <code dir=\"ltr\">&lt;legend&gt;</code> فيه “Ticket type”.</li>\n<li>أضف <code dir=\"ltr\">autocomplete=&quot;name&quot;</code> و<code dir=\"ltr\">autocomplete=&quot;email&quot;</code>، واجعل حقل البريد <code dir=\"ltr\">type=&quot;email&quot;</code>.</li>\n<li>في السكربت، حين يكون البريد غير صالح: اضبط <code dir=\"ltr\">aria-invalid=&quot;true&quot;</code>، واجعل <code dir=\"ltr\">aria-describedby</code> تشير إلى <code dir=\"ltr\">#email-error</code>، وانقل التركيز إلى حقل البريد. وحين يصبح صالحًا، احذف الخاصيتين.</li>\n</ol>\n","hints":["إما <label for=\"email\">Email</label> قبل حقل الإدخال، أو لُفّ زر الـ radio بـ <label> … Standing</label>.","يجب أن يكون الـ legend أول عنصر ابن داخل الـ fieldset.","في فرع الخطأ: email.setAttribute(‘aria-invalid’, ‘true’); email.setAttribute(‘aria-describedby’, ‘email-error’); email.focus();"],"explanation":"<p>الـ labels تمنح كل حقل اسمًا يبقى بعد الكتابة، والـ fieldset يتيح لقارئات الشاشة أن تُعلن “Ticket type, grouping” قبل الخيارات. وقيم autocomplete تتيح للمتصفّحات ومديري كلمات المرور تعبئة النموذج، وهذا ما يطلبه معيار 1.3.5. صار السكربت يربط الخطأ بثلاث طرق: <code dir=\"ltr\">aria-invalid</code> تقول إن الحقل خاطئ، و<code dir=\"ltr\">aria-describedby</code> تجعل الرسالة وصفًا له، و<code dir=\"ltr\">focus()</code> تأخذ المستخدم إليه مباشرة، فيقرأ NVDA: “Email, edit text, invalid entry, Enter an email address like name@example.com”. وحذف الخاصيتين عند النجاح يمنع قراءة الخطأ القديم مرة أخرى، لأن العنصر المُشار إليه يُعلَن حتى وهو مخفي.</p>\n","checkLabels":{"labels":"لحقول الإدخال الأربعة labels بالنص الصحيح","fieldset":"أزرار الـ radio داخل fieldset أول عنصر ابن فيه هو الـ legend “Ticket type”","autocomplete":"لحقلي الاسم والبريد قيم autocomplete، وحقل البريد type=“email”","error-linked":"البريد غير الصالح يضبط aria-invalid ويربط رسالة الخطأ المرئية بـ aria-describedby","error-focus":"ينتقل التركيز إلى حقل البريد بعد المحاولة الفاشلة","error-clears":"البريد الصالح يحذف aria-invalid ويُخفي رسالة الخطأ"}},"words":620,"readMinutes":4}