{"id":"l-ds-2-1","courseId":"design-systems","lang":"ar","kind":"lesson","summary":"نظّم الـ design tokens في ثلاث طبقات primitive وsemantic وcomponent على شكل CSS custom properties، حتى يُحدّث تغييرٌ واحد في النيّة كلَّ مكوّن يتشاركها.","html":"<p>كان أول ملف tokens في Northwind قائمة ألوان: <code dir=\"ltr\">--blue-600</code>، <code dir=\"ltr\">--gray-200</code>، <code dir=\"ltr\">--red-500</code>. وكل مكوّن استخدمها مباشرة. بدا ذلك مرتّبًا حتى جاء الاستحواذ على Tidewater، وسأل أحدهم سؤالًا بسيطًا: «أيّ درجات الأزرق هي أزرق علامة Northwind، وأيّها مجرّد روابط؟». لم يستطع أحد الإجابة دون قراءة 300 ملف. القائمة المسطّحة من الألوان تسجّل القيم، لكنها لا تسجّل القرارات.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الطبقة 1: الـ primitives</h2>\n<p>الـ primitives (القيم الأولية) هي لوحة الألوان والقيم: كل قيمة خام يسمح بها النظام، مسمّاة بحسب ما <em>هي</em>.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1f5ae0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-blue-700</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1848b8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-gray-900</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1f2933</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-gray-200</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#d9dee3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-white</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#ffffff</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-space-2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-space-4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</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>الـ primitives تجيب عن سؤال «ما القيم الموجودة؟». وهي لا تحمل أي معنى عن قصد. فـ <code dir=\"ltr\">--nw-blue-600</code> لا يعرف إن كان زرًا أو رابطًا أو خطًّا في رسم بياني.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الطبقة 2: الـ semantic tokens</h2>\n<p>الـ semantic tokens (الـ tokens الدلالية) تسمّي <em>نيّة</em> وتشير إلى primitive:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg-hover</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-700</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-white</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-gray-900</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-border</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-gray-200</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>هذه الطبقة هي الأكثر استخدامًا، وهي التي تغيّرها الـ themes. حين يصل الوضع الداكن أو علامة Tidewater، تعيد ربط هذه الأسماء بـ primitives مختلفة، ولا تلمس أي مكوّن.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الطبقة 3: الـ component tokens</h2>\n<p>الـ component tokens تسمّي الأجزاء القابلة للتعديل في مكوّن واحد، وتشير إلى الـ semantic tokens:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">.nw-button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-padding-x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-space-4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-space-2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-padding-x</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>الآن صار لكل نوع من ثلاثة أنواع من التغيير مكان واحد واضح:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 230\" role=\"img\" aria-labelledby=\"l-ds-2-1-t1\"><defs><marker id=\"arrow-l-ds-2-1-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-ds-2-1-t1\">الـ component token المسمّى button-bg يشير إلى الـ semantic token المسمّى color-action-bg، الذي يشير إلى الـ primitive المسمّى blue-600 بالقيمة 1f5ae0. كل طبقة تتغيّر لسبب مختلف.</title>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"115\" y=\"63\" text-anchor=\"middle\">--nw-button-bg</text>\n  <rect class=\"d-box-primary\" x=\"235\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"330\" y=\"63\" text-anchor=\"middle\">--nw-color-action-bg</text>\n  <rect class=\"d-box\" x=\"450\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"545\" y=\"63\" text-anchor=\"middle\">--nw-blue-600</text>\n  <path class=\"d-arrow\" d=\"M210 58 L231 58\" marker-end=\"url(#arrow-l-ds-2-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M425 58 L446 58\" marker-end=\"url(#arrow-l-ds-2-1-1)\"/>\n  <text class=\"d-label-strong\" x=\"115\" y=\"120\" text-anchor=\"middle\">Component</text>\n  <text class=\"d-label-strong\" x=\"330\" y=\"120\" text-anchor=\"middle\">Semantic</text>\n  <text class=\"d-label-strong\" x=\"545\" y=\"120\" text-anchor=\"middle\">Primitive</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"150\" text-anchor=\"middle\">تغيير مكوّن واحد</text>\n  <text class=\"d-label-muted\" x=\"330\" y=\"150\" text-anchor=\"middle\">تغيير theme أو علامة</text>\n  <text class=\"d-label-muted\" x=\"545\" y=\"150\" text-anchor=\"middle\">تغيير لوحة الألوان</text>\n  <text class=\"d-code\" x=\"545\" y=\"190\" text-anchor=\"middle\">#1f5ae0</text>\n  <path class=\"d-line\" d=\"M545 86 L545 172\"/>\n</svg><figcaption>كل طبقة تشير فقط إلى الطبقة التي تحتها</figcaption></figure>\n<ul>\n<li><strong>لوحة ألوان جديدة</strong> (أزرق أفضل قليلًا في إمكانية الوصول): غيّر الـ primitive.</li>\n<li><strong>theme جديد أو علامة جديدة</strong> (الوضع الداكن، Tidewater): أعد ربط الـ semantic tokens.</li>\n<li><strong>مكوّن واحد يحتاج أن يختلف</strong> (زر الواجهة الرئيسية في الموقع التسويقي بحشوة أكبر): خصّص الـ token الخاص بذلك المكوّن.</li>\n</ul>\n<p>والقاعدة التي تجعل كل هذا يعمل: <strong>المكوّنات تشير إلى الـ semantic tokens، ولا تشير إلى الـ primitives أبدًا.</strong> في اللحظة التي يستخدم فيها مكوّن <code dir=\"ltr\">--nw-blue-600</code> مباشرة، يتوقّف عن اتّباع الـ themes، وتكون قد أعدت بناء القائمة المسطّحة بأسماء أطول.</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>تعريف الـ component tokens على ‎:root</span></p>\n<p>إن كتبت <code dir=\"ltr\">--nw-button-bg: var(--nw-color-action-bg)</code> داخل <code dir=\"ltr\">:root</code>، فإن المتصفّح يستبدل الـ <code dir=\"ltr\">var()</code> هناك مباشرة، عند الجذر. فيرث كل زر الأزرق المحسوب مسبقًا، ولا يكون لتخصيص <code dir=\"ltr\">--nw-color-action-bg</code> على قسم <code dir=\"ltr\">.promo</code> أي أثر على أزراره. عرّف الـ component tokens على الـ selector الخاص بالمكوّن نفسه، حتى تُحسب قيمتها على كل زر وفق القيم الدلالية التي يحدّدها أسلافه.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">كيف يحسب المتصفّح قيمة الـ token</h2>\n<p>الـ custom properties تُورَّث كما تُورَّث <code dir=\"ltr\">color</code>. حين يحسب المتصفّح أنماط زر ما، يبحث عن <code dir=\"ltr\">--nw-button-bg</code> على الزر نفسه، فيجد <code dir=\"ltr\">var(--nw-color-action-bg)</code>، ثم يبحث عن هذه أيضًا على الزر، حيث تكون القيمة موروثة من أقرب سلف حدّدها. ولهذا يمكنك تغيير theme جزء من الصفحة بتحديد semantic token على حاوية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">.campaign-banner</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-green-600</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>كل زر داخل اللافتة يصبح أخضر، وكل زر خارجها يبقى أزرق. بلا classes جديدة، وبلا تغيير في المكوّنات. وهذا السلوك في تحديد النطاق هو الأساس الذي تقوم عليه الـ themes في الدروس القادمة.</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>كن بخيلًا مع الـ component tokens</span></p>\n<p>لا تكشف Northwind عن component tokens إلا للقيم التي احتاجت الفرق فعلًا إلى تغييرها، وعادةً من اثنين إلى خمسة لكل مكوّن. كل token تنشره وعدٌ عليك الوفاء به في الإصدارات القادمة. يمكنك دائمًا إضافة token لاحقًا؛ أما حذفه فتغيير كاسر.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">ليست الألوان وحدها</h2>\n<p>كل فئة من الـ tokens تُعامل بالطريقة نفسها. الـ primitives الخاصة بالمسافات سُلَّم (<code dir=\"ltr\">--nw-space-1</code> إلى <code dir=\"ltr\">--nw-space-12</code>)؛ والمسافات الدلالية تسمّي دورًا، مثل <code dir=\"ltr\">--nw-space-inset-md</code> للحشوة داخل البطاقة، أو <code dir=\"ltr\">--nw-space-stack-sm</code> للفراغ بين حقول نموذج متراصّة. والـ primitives الخاصة بالطباعة هي عائلات الخطوط وأحجامها وأوزانها؛ والطباعة الدلالية تسمّي أنماطًا مثل <code dir=\"ltr\">--nw-font-body</code> أو <code dir=\"ltr\">--nw-font-heading-lg</code>. وتتبع أنصاف الأقطار والظلال ومُدد الحركة النمط نفسه.</p>\n<p>وللتصوّر: لدى Northwind اليوم نحو 180 primitive، و120 semantic token، و90 component token. الطبقة الدلالية هي حيث يحدث معظم التفكير التصميمي، وهي الطبقة التي يجب أن يلجأ إليها المصمّمون والمهندسون أولًا. وإن ظلّ فريق يطلب primitive بعينه، فهذا يعني عادةً أن semantic token ناقص، والإصلاح الصحيح أن تضيف الدور، لا أن تسلّم القيمة الخام.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">طريقة سريعة لفحص طبقاتك</h2>\n<p>افتح الـ CSS الخاص بأي مكوّن وابحث عن البادئة الخاصة بالـ primitives لديك، مثل <code dir=\"ltr\">--nw-blue</code> أو <code dir=\"ltr\">--nw-gray</code>. في النظام السليم تظهر هذه الأسماء في مكان واحد فقط: تعريفات الـ semantic tokens. تشغّل Northwind هذا الفحص كقاعدة lint في الـ CI، وستتعرّف عليها في القسم 4. في التمرين تضيف الطبقتين الدلالية والخاصة بالمكوّن إلى زر يقفز حاليًا مباشرة إلى primitive. وبعدها تسمّي هذه الـ tokens بطريقة تبقى منطقية بعد ثلاث سنوات.</p>\n","toc":[{"id":"sec-1","text":"الطبقة 1: الـ primitives","level":2},{"id":"sec-2","text":"الطبقة 2: الـ semantic tokens","level":2},{"id":"sec-3","text":"الطبقة 3: الـ component tokens","level":2},{"id":"sec-4","text":"كيف يحسب المتصفّح قيمة الـ token","level":2},{"id":"sec-5","text":"ليست الألوان وحدها","level":2},{"id":"sec-6","text":"طريقة سريعة لفحص طبقاتك","level":2}],"takeaways":["الـ primitive tokens تسمّي القيم الخام (<code dir=\"ltr\">--nw-blue-600</code>)، والـ semantic tokens تسمّي النيّة (<code dir=\"ltr\">--nw-color-action-bg</code>)، والـ component tokens تسمّي مفاتيح التحكّم الخاصة بمكوّن واحد.","المكوّنات تشير إلى الـ semantic tokens ولا تشير إلى الـ primitives أبدًا، فيقتصر تغيير الـ theme على الطبقة الدلالية.","عرّف الـ component tokens على الـ selector الخاص بالمكوّن لا على <code dir=\"ltr\">:root</code>، حتى تُحسب قيمتها وفق أي تخصيص في ذلك الجزء من الصفحة.","أضف الـ component tokens باقتصاد؛ فكل واحد منها API عام ستلتزم بدعمه."],"further":[{"title":"Using CSS custom properties (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties"},{"title":"var() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/var"},{"title":"Design tokens (Material Design 3)","url":"https://m3.material.io/foundations/design-tokens"}],"quiz":[{"q":"<p>يريد فريق التسويق أن يتحوّل كل إجراء أساسي في الموقع إلى الأخضر خلال حملة. في نظام من ثلاث طبقات، أيّ طبقة تغيّر؟</p>","options":[{"text":"طبقة الـ primitives، بتغيير <code dir=\"ltr\">--nw-blue-600</code> إلى أخضر.","why":"هذا يحوّل كل استخدام لذلك الأزرق إلى أخضر، بما فيها الروابط وشارات المعلومات التي ليست إجراءات، ويجعل الاسم كاذبًا."},{"text":"الطبقة الدلالية، بجعل <code dir=\"ltr\">--nw-color-action-bg</code> يشير إلى primitive أخضر.","why":"صحيح. كل مكوّن يعبّر عن «إجراء أساسي» يتبع التغيير، ولا شيء غيره."},{"text":"طبقة الـ component، بتخصيص الخلفية لكل مكوّن على حدة.","why":"هذا ينجح، لكنك ستضطر إلى إيجاد كل مكوّن فيه لون إجراء، وهي بالضبط المشكلة التي وُجدت الطبقات لحلّها."},{"text":"لا شيء منها؛ أضف class جديدًا باسم <code dir=\"ltr\">.green</code> لكل زر.","why":"الـ classes المؤقّتة تتجاوز النظام كليًا، وتترك التنظيف لما بعد الحملة."}],"answer":1},{"q":"<p>لماذا يبقى الزر داخل <code dir=\"ltr\">.promo</code> أزرق؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</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:#0550AE;--shiki-dark:#79C0FF\">.promo</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-green-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.nw-button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span></code></pre></div>\n","options":[{"text":"الـ custom properties لا تُورَّث إلى الأزرار.","why":"الـ custom properties تُورَّث افتراضيًا، والأزرار ضمنها؛ والتوريث هو بالضبط ما يوصل الأزرق."},{"text":"الـ specificity الخاصة بـ <code dir=\"ltr\">.promo</code> أقل من <code dir=\"ltr\">:root</code>.","why":"القاعدتان تستهدفان عنصرين مختلفين، فلا تُقارن الـ specificity أصلًا؛ وكل تعريف يُطبَّق حيث كُتب."},{"text":"المتصفّح يخزّن أول قيمة لكل custom property مؤقتًا.","why":"لا يوجد تخزين مؤقت؛ القيم تُحسب لكل عنصر أثناء الـ cascade العادي."},{"text":"حُسبت قيمة <code dir=\"ltr\">--nw-button-bg</code> على <code dir=\"ltr\">:root</code>، فورثت الأزرار الأزرق المحسوب مسبقًا.","why":"صحيح. الـ <code dir=\"ltr\">var()</code> داخل custom property يُستبدل حيث عُرّف؛ انقل <code dir=\"ltr\">--nw-button-bg</code> إلى <code dir=\"ltr\">.nw-button</code> وستُحسب قيمته وفق تخصيص <code dir=\"ltr\">.promo</code>."}],"answer":3},{"q":"<p>يقترح زميل component token لكل خاصية CSS في كل مكوّن. ما التكلفة الرئيسية؟</p>","options":[{"text":"تتباطأ المتصفّحات بوضوح مع أكثر من 100 custom property.","why":"المتصفّحات تتعامل مع آلاف الـ custom properties بلا مشكلة؛ الأداء ليس القضية بهذا الحجم."},{"text":"يصبح كل token جزءًا من API عام تعتمد عليه الفرق، فيصعب تغيير النظام.","why":"صحيح. كل مفتاح تحكّم مكشوف وعدٌ؛ وإعادة تسميته أو حذفه لاحقًا تغيير كاسر (breaking change)."},{"text":"الـ component tokens لا يمكنها الإشارة إلى الـ semantic tokens.","why":"يمكنها، ويجب أن تفعل؛ فهكذا ترتبط الطبقات ببعضها."}],"answer":1}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<section class=\"app\">\n  <button class=\"nw-button\" id=\"main\">Assign driver</button>\n</section>\n<section class=\"promo\">\n  <button class=\"nw-button\" id=\"promo\">Start free trial</button>\n</section>","css":"/* Tier 1: primitives (done) */\n:root {\n  --nw-blue-600: #1f5ae0;\n  --nw-blue-700: #1848b8;\n  --nw-white: #ffffff;\n  --nw-space-2: 0.5rem;\n  --nw-space-4: 1rem;\n}\n\n/* Tier 2: add semantic tokens here */\n\n/* Tier 3: the button skips straight to primitives. Fix it. */\n.nw-button {\n  background: var(--nw-blue-600);\n  color: var(--nw-white);\n  padding: var(--nw-space-2) var(--nw-space-4);\n  border: 0;\n  border-radius: 6px;\n  font: inherit;\n}\n.promo { margin-top: 16px; }\n","js":""},"solution":{"html":"<section class=\"app\">\n  <button class=\"nw-button\" id=\"main\">Assign driver</button>\n</section>\n<section class=\"promo\">\n  <button class=\"nw-button\" id=\"promo\">Start free trial</button>\n</section>","css":"/* Tier 1: primitives (done) */\n:root {\n  --nw-blue-600: #1f5ae0;\n  --nw-blue-700: #1848b8;\n  --nw-white: #ffffff;\n  --nw-space-2: 0.5rem;\n  --nw-space-4: 1rem;\n}\n\n/* Tier 2: semantic tokens */\n:root {\n  --nw-color-action-bg: var(--nw-blue-600);\n  --nw-color-action-text: var(--nw-white);\n}\n\n/* Tier 3: component tokens, declared on the component */\n.nw-button {\n  --nw-button-bg: var(--nw-color-action-bg);\n  --nw-button-text: var(--nw-color-action-text);\n\n  background: var(--nw-button-bg);\n  color: var(--nw-button-text);\n  padding: var(--nw-space-2) var(--nw-space-4);\n  border: 0;\n  border-radius: 6px;\n  font: inherit;\n}\n.promo { margin-top: 16px; }\n","js":""},"checks":[{"id":"still-blue","test":"return style('#main', 'background-color') === 'rgb(31, 90, 224)' && style('#main', 'color') === 'rgb(255, 255, 255)';"},{"id":"semantic","test":"const cs = getComputedStyle(document.documentElement); return cs.getPropertyValue('--nw-color-action-bg').trim() !== '' && cs.getPropertyValue('--nw-color-action-text').trim() !== '';"},{"id":"component","test":"const b = $('#main'); b.style.setProperty('--nw-button-bg', 'rgb(4, 5, 6)'); const ok = style(b, 'background-color') === 'rgb(4, 5, 6)'; b.style.removeProperty('--nw-button-bg'); return ok;"},{"id":"follows-semantic","test":"const p = $('.promo'); p.style.setProperty('--nw-color-action-bg', 'rgb(10, 130, 60)'); const ok = style('#promo', 'background-color') === 'rgb(10, 130, 60)' && style('#main', 'background-color') === 'rgb(31, 90, 224)'; p.style.removeProperty('--nw-color-action-bg'); return ok;"},{"id":"no-primitives","test":"const rules = [...document.styleSheets].flatMap((s) => [...s.cssRules]).filter((r) => r.selectorText); const btn = rules.filter((r) => r.selectorText.includes('nw-button')); return btn.length > 0 && btn.every((r) => !/--nw-(blue|white)/.test(r.style.cssText));"}],"title":"امنح الزر ثلاث طبقات","prompt":"<p>الـ primitives جاهزة، لكن <code dir=\"ltr\">.nw-button</code> يقفز مباشرة إلى <code dir=\"ltr\">--nw-blue-600</code>، فلا يستطيع أي theme أن يغيّره. أضف الطبقتين الناقصتين:</p>\n<ol>\n<li>على <code dir=\"ltr\">:root</code>، عرّف الـ semantic tokens المسمّاة <code dir=\"ltr\">--nw-color-action-bg</code> و<code dir=\"ltr\">--nw-color-action-text</code>، واجعلها تشير إلى الـ primitives المناسبة.</li>\n<li>على <code dir=\"ltr\">.nw-button</code>، عرّف الـ component tokens المسمّاة <code dir=\"ltr\">--nw-button-bg</code> و<code dir=\"ltr\">--nw-button-text</code>، واجعلها تشير إلى الـ semantic tokens، واستخدمها في <code dir=\"ltr\">background</code> و<code dir=\"ltr\">color</code>.</li>\n</ol>\n<p>يجب أن يبدو الزر تمامًا كما هو الآن، ولا يجوز لأي قاعدة من قواعد <code dir=\"ltr\">.nw-button</code> أن تذكر primitive من نوع <code dir=\"ltr\">--nw-blue-…</code> أو <code dir=\"ltr\">--nw-white</code>.</p>\n","hints":["الـ semantic tokens تبدو هكذا داخل <code dir=\"ltr\">:root</code>: <code dir=\"ltr\">--nw-color-action-bg: var(--nw-blue-600);</code>.","الـ component tokens توضع داخل قاعدة <code dir=\"ltr\">.nw-button { … }</code> لا على <code dir=\"ltr\">:root</code>، حتى تُحسب قيمتها على كل زر.","ثم غيّر <code dir=\"ltr\">background: var(--nw-blue-600)</code> إلى <code dir=\"ltr\">background: var(--nw-button-bg)</code>، وافعل الشيء نفسه مع <code dir=\"ltr\">color</code>."],"explanation":"<p>صار الزر يحسب <code dir=\"ltr\">--nw-button-bg</code> ← <code dir=\"ltr\">--nw-color-action-bg</code> ← <code dir=\"ltr\">--nw-blue-600</code>. ولأن الـ component tokens معرّفة على <code dir=\"ltr\">.nw-button</code>، يبحث كل زر عن الـ semantic token لدى أسلافه هو، فيغيّر تحديد <code dir=\"ltr\">--nw-color-action-bg</code> على <code dir=\"ltr\">.promo</code> لون زر الـ promo وحده. لو عرّفت <code dir=\"ltr\">--nw-button-bg</code> على <code dir=\"ltr\">:root</code> لحُسبت القيمة مرة واحدة عند الجذر، ولما فعل تخصيص الـ promo شيئًا.</p>\n","checkLabels":{"still-blue":"ما زال الزر أزرق بنص أبيض","semantic":"يعرّف <code dir=\"ltr\">:root</code> كلًّا من <code dir=\"ltr\">--nw-color-action-bg</code> و<code dir=\"ltr\">--nw-color-action-text</code>","component":"خلفية الزر تأتي من <code dir=\"ltr\">--nw-button-bg</code>","follows-semantic":"تخصيص <code dir=\"ltr\">--nw-color-action-bg</code> على <code dir=\"ltr\">.promo</code> يغيّر لون زر الـ promo وحده","no-primitives":"لا توجد قاعدة من قواعد <code dir=\"ltr\">.nw-button</code> تشير إلى primitive token"}},"words":665,"readMinutes":4}