EduFlow
أساسيات React
⁦0%⁩

الوحدة 4 · إطلاق مشروع حقيقي

التنسيق باستخدام Tailwind

17 دقراءة 4 داملأ الفراغات

الدرس في لمحة

أضف Tailwind CSS v4 إلى مشروع Vite عبر الـ plugin الرسمي، ونسّق Watchlist بالـ utility classes ومتغيّرات الحالة (variants)، وخصّص السمة داخل CSS.

  1. 1ثبّته
  2. 2قراءة الـ utility classes
  3. 3الـ variants: الشروط في بادئة
  4. 4الـ design tokens الخاصة بك
  5. 5المقايضة

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

Watchlist يعمل، لكنه يبدو كأنه من عام 1998. يمكنك كتابة stylesheet، وكثير من التطبيقات الممتازة تفعل ذلك. لكن هذه الدورة تستخدم Tailwind CSS، لأنه شائع جدًا في مشاريع React التي ستنضمّ إليها، ولأن التنسيق داخل الـ JSX مباشرة يُبقي الـ components مكتفية بذاتها: ملف MovieItem يحمل الـ markup والسلوك والمظهر معًا.

Tailwind مجموعة كبيرة من الـ utility classes الصغيرة، كلٌّ منها تفعل شيئًا واحدًا: p-4 تضيف حشوًا، وrounded-lg تدوّر الزوايا، وtext-slate-600 تضبط لونًا. تركّبها داخل className. ويمسح Tailwind ملفاتك، ويعثر على الـ classes التي استخدمتها، ويولّد stylesheet لا يحتوي إلا عليها.

ثبّته

لـ Tailwind v4 plugin رسمي لـ Vite، والإعداد ثلاث خطوات. ثبّت الحزم:

Terminal
npm install tailwindcss @tailwindcss/vite

أضف الـ plugin إلى إعدادات Vite:

vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
});

واستورد Tailwind في أعلى ملف الأنماط الرئيسي، الذي يستورده main.jsx أصلًا:

src/index.css
@import "tailwindcss";

هذا كل شيء. لا يوجد tailwind.config.js ولا إعدادات PostCSS في v4 افتراضيًا. يعثر Tailwind على ملفات المصدر بنفسه. ويعيد Vite تشغيل خادم التطوير تلقائيًا حين تحفظ vite.config.js؛ وإن لم تظهر الأنماط بعد ذلك، فأوقفه وشغّل npm run dev من جديد.

قراءة الـ utility classes

هذا هو الهيكل الخارجي لـ Watchlist:

src/App.jsx
<main className="mx-auto max-w-xl px-4 py-10">
  <h1 className="text-3xl font-bold tracking-tight text-slate-900">Watchlist</h1>
  <p className="mt-1 text-sm text-slate-500">{left} to watch</p>
  {/* …form, filters, list */}
</main>

الأسماء مختصرة لكنها منهجية، وبعد يوم واحد ستقرؤها بسرعة قراءة CSS:

  • المسافات تستخدم مقياسًا كل خطوة فيه 0.25rem: p-4 حشو مقداره 1rem، وmt-1 هامش علوي مقداره 0.25rem، وpx-4 حشو أفقي. وmx-auto يوسّط الكتلة.
  • الأحجام والخطوط: max-w-xl تحدّ العرض، وtext-3xl تضبط حجم الخط، وfont-bold سُمكه.
  • الألوان تأتي من لوحة بدرجات من 50 إلى 950: text-slate-500 وbg-indigo-600.

ويستطيع محرّرك المساعدة: إضافة Tailwind CSS IntelliSense الرسمية لـ VS Code تكمل أسماء الـ classes تلقائيًا وتعرض الـ CSS الذي تنتجه كل واحدة حين تمرّر المؤشّر عليها.

الـ variants: الشروط في بادئة

ضع بادئة قبل الـ class لتطبيقه تحت شرط فقط:

src/components/MovieItem.jsx
<li className="flex items-center gap-3 rounded-lg border border-slate-200 bg-white p-3 dark:border-slate-700 dark:bg-slate-800">
  <span className={movie.watched ? 'flex-1 text-slate-400 line-through' : 'flex-1 text-slate-900 dark:text-slate-100'}>
    {movie.title}
  </span>
  <button
    aria-pressed={movie.watched}
    onClick={() => onToggle(movie.id)}
    className="rounded-md border border-slate-300 px-3 py-1 text-sm hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-indigo-500 aria-pressed:border-indigo-600 aria-pressed:bg-indigo-600 aria-pressed:text-white"
  >
    {movie.watched ? 'Watched' : 'Mark watched'}
  </button>
</li>
  • تتعامل hover: وfocus-visible: مع حالات التفاعل. امنح العناصر التفاعلية دائمًا نمط تركيز مرئيًا لمستخدمي لوحة المفاتيح.
  • تُطبَّق dark: حين يكون نظام المستخدم في الوضع الداكن (تتبع prefers-color-scheme افتراضيًا).
  • تُطبَّق aria-pressed: حين تكون aria-pressed في العنصر true. فيتبع مظهر زر التبديل الخاصية نفسها التي تعلنها الـ screen readers، ولا يمكن أن يختلفا.
  • نقاط التوقّف تبدأ من الجوّال: sm: وmd: وlg: تُطبَّق من ذلك العرض فما فوق، لذا اكتب تخطيط الهاتف أولًا، وأضف البادئات للشاشات الأكبر.

أمّا الشروط المعتمدة على الـ state في React، مثل movie.watched على الـ span، فهي معامل ثلاثي عادي يختار بين نصّين كاملين من الـ classes.

الـ design tokens الخاصة بك

خصّص السمة داخل CSS باستخدام @theme. كل متغيّر يصبح utilities تلقائيًا:

src/index.css
@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.62 0.19 285);
  --color-brand-600: oklch(0.55 0.21 285);
  --font-display: "Inter", system-ui, sans-serif;
}

الآن تعمل bg-brand-600 وtext-brand-500 وfont-display كأنها classes مدمجة. عرّف عددًا قليلًا من الـ tokens للون علامتك التجارية وخطوطك، بدل أن تنثر قيمًا عابرة مثل bg-[#6d28d9] في أنحاء الـ components.

المقايضة

قوائم الـ classes الطويلة هي الثمن الصريح. قد يحمل زر منسَّق خمسة عشر class. والحلّ ليس إخفاءها في CSS باستخدام @apply؛ بل ما تدرّبت عليه طوال الدورة: استخرج component. الـ component المسمّى Button يحمل هذه الـ classes الخمسة عشر في مكان واحد، وفي كل مكان آخر تكتب <Button>. الـ components هي وحدة إعادة الاستخدام في React، للأنماط تمامًا كما للسلوك.

Tailwind ليس الجواب الجيد الوحيد. الـ CSS Modules (مدمجة في Vite: سمِّ الملف MovieItem.module.css واستورده) تعطيك أسماء classes محصورة النطاق مع CSS عادي، وكثير من الفرق تفضّلها. والمهارات تنتقل في الحالتين: markup دلالي، ومقياس متّسق للمسافات والألوان، وحالات تركيز مرئية، وتخطيط يعمل على الهاتف أولًا.

صار Watchlist يبدو كمنتج حقيقي. بقي شيء واحد: وضعه على الإنترنت، وهذا موضوع الدرس التالي.

أهم النقاط

  • يحتاج Tailwind v4 إلى npm install tailwindcss @tailwindcss/vite، والـ plugin داخل vite.config.js، و@import "tailwindcss"; في ملف CSS؛ ولا حاجة إلى ملف إعدادات.
  • كل utility class تقابل تصريح CSS واحدًا على مقياس مشترك، مثل p-4 لحشو مقداره 1rem، وtext-slate-600 للون من لوحة الألوان.
  • البادئات تطبّق الـ class بشرط، مثل md: فوق نقطة توقّف، وhover: عند التمرير، وdark: في الوضع الداكن، وaria-pressed: حين تكون تلك الخاصية true.
  • يعثر Tailwind على الـ classes بمسح ملفاتك كنص، لذا اكتب دائمًا أسماء classes كاملة ولا تبنِها أبدًا بدمج النصوص.
  • خصّص الألوان والخطوط وغيرها من الـ tokens بمتغيّرات CSS داخل كتلة @theme.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

ثبّتّ الحزمتين وأضفت @import "tailwindcss"; إلى src/index.css، لكن لا تأثير لأي utility class. ما الخطوة الناقصة على الأرجح؟

السؤال 2 من 4

يبني component اسم class بالشكل `text-${color}-600`، حيث color قيمتها ‘red’ أو ‘green’. في الإنتاج لا لون للنص. لماذا؟

السؤال 3 من 4

ماذا يفعل className="bg-white md:bg-slate-100 dark:bg-slate-900"؟

السؤال 4 من 4

تضبط أزرار الفلترة لديك aria-pressed. ما أنظف طريقة لتنسيق الزر النشط باستخدام Tailwind؟

تدريب

املأ الفراغات

اربط Tailwind v4

املأ الفراغات لإضافة Tailwind CSS v4 إلى مشروع Vite الخاص بـ Watchlist، ثم نسّق زر الفلترة النشط باستخدام الخاصية الموجودة عليه أصلًا.

// Terminal // npm install @tailwindcss/vite // vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from ''; export default defineConfig({ plugins: [react(), ], }); /* src/index.css */ @import ""; // src/components/FilterButton.jsx: style the active filter <button aria-pressed={active} className="px-3 py-1 :bg-indigo-600">