Tailwind v4 الوضع الداكن: إعداد، تبديل وإصلاحات (2026)
١٥ يونيو ٢٠٢٦
في Tailwind CSS v4، لم يعد خيار الإعداد darkMode يُطبق افتراضيًا — فالإصدار الرابع يعتمد على "CSS أولاً". قم بتمكين وضع الظلام القائم على الفئات (class-based) عن طريق إضافة @custom-variant dark (&:where(.dark, .dark *)); إلى ملف CSS الخاص بك بعد @import "tailwindcss";، ثم قم بتبديل فئة dark على عنصر <html>. بدون ذلك، سيتبع dark: تفضيلات النظام فقط.
ملخص
لا يزال Tailwind CSS v4 يشحن متغير dark:، لكنه نقل الإعدادات من tailwind.config.js إلى ملف CSS الخاص بك. افتراضيًا، يتتبع dark: نظام التشغيل عبر prefers-color-scheme بدون أي جافا سكريبت.1 لتشغيل وضع الظلام من فئة dark بدلاً من ذلك، أضف سطرًا واحدًا — @custom-variant dark (&:where(.dark, .dark *)); — إلى ورقة الأنماط الخاصة بك.1 السبب وراء معظم تقارير "وضع الظلام في Tailwind v4 لا يعمل" هو أن مفتاح الإعداد القديم darkMode: 'class' لم يعد جزءًا من نموذج "CSS أولاً" في الإصدار الرابع، لذا تفقد المشاريع التي تمت ترقيتها ميزة تبديل الفئات حتى يتم إضافة هذا السطر.2 تم التحقق من هذا الدليل مقابل Tailwind CSS v4.3.13 وتم تجميع وفحص كل قصاصة كود CSS في 15 يونيو 2026.
ما ستتعلمه
- لماذا يتوقف
dark:عن العمل بعد الترقية إلى Tailwind v4، والحل المكون من سطر واحد - أين يتم إعداد وضع الظلام الآن بعد أن أصبح الإصدار الرابع يعتمد على "CSS أولاً" ولا يتم تحميل
tailwind.config.jsافتراضيًا - كيف يعمل سلوك
prefers-color-schemeالافتراضي بدون أي جافا سكريبت - كيفية إضافة زر تبديل لوضع الظلام يعتمد على الفئات وحفظه في
localStorage - كيفية منع وميض السمة الخاطئة (FOUC) باستخدام سكربت
<head>مضمن - كيفية بناء زر تبديل ثلاثي (فاتح / داكن / نظام)
- كيفية استخدام سمة
data-themeبدلاً من الفئة - أكثر أخطاء وضع الظلام شيوعًا في Tailwind v4، مع حلولها
لماذا "لا يعمل" وضع الظلام في Tailwind v4؟
تحدث معظم حالات "عدم عمل وضع الظلام في Tailwind v4" بسبب تغيير واحد: مفتاح إعداد darkMode لم يعد جزءًا من الإعداد الافتراضي. يعتمد Tailwind v4 على "CSS أولاً"، ولا يتم تحميل ملف tailwind.config.js تلقائيًا، لذا فإن darkMode: 'class' ليس له أي تأثير ما لم تختر صراحةً العودة إلى الإعدادات القديمة باستخدام توجيه @config.2 لا تزال أدوات dark:bg-gray-900 الخاصة بك تُجمع — لكنها تعتمد افتراضيًا على استعلام وسائط النظام، لذا فإن تبديل فئة .dark لن يفعل شيئًا حتى تقوم بتفعيل الخيار.
هناك سبب ثانٍ وأكثر دهاءً. قامت أداة الترقية التلقائية (@tailwindcss/upgrade)، في بعض المشاريع، بإعادة كتابة الإعداد القديم القائم على الفئات إلى متغير معطل مثل @custom-variant dark (@media not print { .dark & })، والذي لا يتصرف كزر تبديل للفئات. إذا قمت بالترقية وتوقف زر التبديل عن العمل بصمت، افتح ملف CSS الخاص بك وتحقق من الشكل الذي تمت إعادة كتابة المتغير إليه.4
لتأكيد ما ينتجه البناء (build) الخاص بك فعليًا، انظر إلى ملف CSS المجمع. بدون متغير مخصص، يتم تجميع أداة dark: إلى استعلام وسائط prefers-color-scheme:
/* dark:bg-gray-900 with NO @custom-variant — system-driven */
.dark\:bg-gray-900 {
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-900);
}
}
إذا كنت تريد أن تتحكم فئة ما في السمة ولا تزال ترى @media (prefers-color-scheme: dark) في المخرجات، فهذا يعني أن تجاوز المتغير (variant override) مفقود أو مشوه.
أين يتم إعداد وضع الظلام في Tailwind v4؟
تقوم بإعداد وضع الظلام في ملف CSS الخاص بك، وليس في ملف إعداد جافا سكريبت. افتح ورقة الأنماط حيث تستورد Tailwind وقم بتجاوز متغير dark مباشرة أسفل الاستيراد:1
/* app.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
هذا السطر الوحيد @custom-variant هو الإعداد الكامل القائم على الفئات. بعد إضافته، يتم تطبيق أدوات dark: كلما ظهرت فئة dark في مكان سابق في شجرة HTML:1
<html class="dark">
<body>
<div class="bg-white dark:bg-gray-900">
<!-- styled for dark mode -->
</div>
</body>
</html>
قد ترى دروسًا تعليمية أخرى تكتب التجاوز كـ &:is(.dark *). هذا يعمل، لكن :is() يتبنى خصوصية (specificity) وسيطه الأكثر تحديدًا، مما يرفع خصوصية كل أداة dark: ويمكن أن يسبب أخطاء تجاوز محيرة لاحقًا. الصيغة الرسمية &:where(.dark, .dark *) تستخدم :where()، والتي تتمتع دائمًا بخصوصية صفرية، لذا تظل أدوات الظلام الخاصة بك بنفس خصوصية نظيراتها الفاتحة تمامًا.5 يفضل استخدام الصيغة الرسمية.
إذا كنت تقوم بإعداد مشروع جديد بدلاً من إصلاح مشروع موجود، فقم بتثبيت حزم v4 أولاً36 واستورد Tailwind مرة واحدة في ملف CSS الرئيسي الخاص بك:
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import { defineConfig from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});
/* src/style.css — the single @import replaces the old base/components/utilities trio */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
للحصول على شرح أعمق لإعداد "CSS أولاً"، وتوجيه @theme، ومسارات @source، راجع الدليل المصاحب Tailwind v4 + Vite من الصفر.
هل يعمل وضع الظلام في Tailwind v4 بدون جافا سكريبت؟
نعم. افتراضيًا، وقبل إضافة أي تجاوز @custom-variant، يتم تشغيل متغير dark: بالكامل بواسطة ميزة الوسائط prefers-color-scheme، لذا فهو يتبع نظام التشغيل بدون جافا سكريبت وبدون أي فئة على الإطلاق.1 إذا كان مطلبك الوحيد هو "احترام سمة نظام تشغيل المستخدم"، فلن تحتاج إلى زر تبديل أو سكربت أو localStorage — فقط اكتب أدوات dark: الخاصة بك وانطلق:
<!-- Follows the OS theme automatically, zero JS -->
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
<h1 class="text-2xl font-bold">Zero Gravity</h1>
<p class="text-gray-500 dark:text-gray-400">Writes in any orientation.</p>
</div>
يتم دعم prefers-color-scheme عبر جميع المتصفحات الحالية، لذا فإن هذا المسار آمن للاستخدام كأساس.7 أضف تجاوز @custom-variant فقط عندما تريد أن يقوم المستخدمون بتبديل السمة يدويًا.
كيفية إضافة زر تبديل لوضع الظلام يعتمد على الفئات؟
للسماح للمستخدمين بتبديل السمات، قم بإضافة أو إزالة فئة dark على عنصر <html> واحفظ الاختيار. مع وجود سطر @custom-variant dark (&:where(.dark, .dark *));، يبدو زر التبديل البسيط كالتالي:
<button id="theme-toggle" type="button"
class="rounded-md border px-3 py-2 dark:border-gray-700">
Toggle theme
</button>
// theme-toggle.js
const root = document.documentElement;
document.getElementById("theme-toggle").addEventListener("click", () => {
const isDark = root.classList.toggle("dark");
localStorage.theme = isDark ? "dark" : "light";
});
هذا الكود مستقل عن إطارات العمل: نفس الـ classList.toggle("dark") يعمل في HTML العادي، أو React، أو Astro، أو Vue، أو Svelte، لأن Tailwind يهتم فقط بوجود فئة dark على عنصر سلف. الكتابة في localStorage هي ما يجعل الاختيار يستمر بعد إعادة تحميل الصفحة — ولكنها بمفردها تسبب وميضًا، وهو ما يعالجه القسم التالي.
كيفية منع وميض وضع الظلام (FOUC)؟
يحدث وميض الثيم الخاطئ لأن سكربت التبديل الخاص بك يعمل بعد أن يكون المتصفح قد رسم الصفحة بالفعل بالثيم الافتراضي. الحل هو تعيين كلاس dark قبل الرسم الأول عن طريق تضمين سكربت صغير في <head>، فوق ملف الأنماط (stylesheet) الخاص بك:1
<head>
<!-- Runs before paint — no flash of the wrong theme -->
<script>
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
<link rel="stylesheet" href="/style.css" />
</head>
لأن هذا السكربت مضمن (inline) ومتزامن في الـ <head>، فإنه ينفذ قبل رندر الـ body، لذا يتم تطبيق الثيم الصحيح عند أول رسم تماماً. احتفظ به مضمناً — تحميله كملف خارجي يعيد ظهور الوميض، لأن المتصفح قد يرسم الصفحة قبل وصول الملف. في التطبيقات التي تعتمد على الرندر من جهة السيرفر (server-rendered)، يمكنك تحقيق نفس النتيجة عن طريق رندر class="dark" على وسم <html> من السيرفر بناءً على التفضيل المخزن، وهو نمط متوافق مع SSR لأطر العمل مثل المذكور في دليل البث والتخزين المؤقت في Next.js.1
كيف تدعم ثيمات الفاتح، والداكن، والنظام؟
يوفر زر التبديل الكامل ثلاث حالات — فاتح صريح، داكن صريح، و"اتباع نظامي". توثيقات Tailwind تتبع هذا النموذج باستخدام ثلاث عمليات لـ localStorage: تعيين theme إلى "light"، أو تعيينه إلى "dark"، أو إزالته تماماً للعودة إلى إعدادات نظام التشغيل.1 تعبير التهيئة الرسمي يتعامل مع الحالات الثلاث:1
// Best inlined in <head> to avoid FOUC.
// dark when: explicit "dark", OR no stored choice AND the OS prefers dark.
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches),
);
قم بربط كل زر بعملية localStorage المقابلة، ثم أعد تشغيل تعبير التبديل:
// theme-controls.js
function applyTheme() {
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches),
);
}
document.getElementById("light").onclick = () => { localStorage.theme = "light"; applyTheme(); };
document.getElementById("dark").onclick = () => { localStorage.theme = "dark"; applyTheme(); };
document.getElementById("system").onclick = () => { localStorage.removeItem("theme"); applyTheme(); };
// Keep "system" mode live when the OS theme changes while the page is open
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if (!("theme" in localStorage)) applyTheme();
});
مراقب التغيير في matchMedia هو التفصيل الذي تتجاهله معظم الشروحات: في وضع "النظام"، يجب أن تتحدث الصفحة إذا قام المستخدم بتغيير ثيم نظام التشغيل الخاص به بينما التبويب مفتوح. window.matchMedia وحدث الـ change الخاص به مدعومان في جميع المتصفحات الحالية.8
كيف تستخدم سمة بيانات (data attribute) بدلاً من الكلاس؟
إذا كنت تفضل data-theme="dark" على الكلاس — وهو أمر شائع في أنظمة التصميم التي تستخدم بالفعل سمات البيانات للثيمات — فقم بتجاوز المتغير (variant) باستخدام محدد سمة بدلاً من ذلك:1
@import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
<html data-theme="dark">
<body>
<div class="bg-white dark:bg-black"><!-- ... --></div>
</body>
</html>
كل شيء آخر — التبديل، سكربت الـ FOUC، منطق الحالات الثلاث — يبقى كما هو؛ أنت فقط تقوم بتعيين document.documentElement.dataset.theme = "dark" بدلاً من تبديل الكلاس. غلاف :where() يحافظ على الخصوصية (specificity) عند الصفر هنا أيضاً.5
الأخطاء الشائعة في الوضع الداكن لـ Tailwind v4 (وإصلاحاتها)
كل صف أدناه يمثل حالة فشل حقيقية مستخلصة من المشكلات التي يبلغ عنها الأشخاص بعد الترقية.
| العرض | السبب | الإصلاح |
|---|---|---|
dark: يتجاهل كلاس .dark | لا يوجد تجاوز لـ @custom-variant — dark: يعتمد افتراضياً على استعلام الوسائط (media query) | أضف @custom-variant dark (&:where(.dark, .dark *)); بعد @import "tailwindcss";1 |
| التبديل كان يعمل في v3، وتعطل بعد الترقية | darkMode: 'class' ليس جزءاً من إعدادات v4 التي تعتمد على CSS أولاً | انقل الإعداد إلى CSS عبر @custom-variant2 |
| يبدو أن المتغير قد تم تعيينه ولكنه لا يزال يفشل | أداة الترقية أنتجت @custom-variant dark (@media not print { .dark & }) | استبدله بالصيغة الرسمية &:where(.dark, .dark *)4 |
| أدوات الداكن (dark utilities) تتجاوز بشكل غير متوقع | استخدام &:is(.dark *)، مما يرفع الخصوصية | انتقل إلى &:where(...) للحصول على خصوصية صفرية5 |
| الصفحة تومض باللون الفاتح ثم الداكن عند التحميل | سكربت التبديل يعمل بعد الرسم الأول | ضمن سكربت الثيم في <head> فوق ملف الأنماط الخاص بك1 |
| وضع "النظام" يتجاهل تغييرات نظام التشغيل المباشرة | لا يوجد مراقب لحدث change في matchMedia | أضف مراقب change يعيد التطبيق عندما لا يكون هناك خيار مخزن8 |
الخلاصة
إصدار Tailwind v4 لم يقم بإزالة الوضع الداكن (dark mode) — بل قام بنقل مفتاح التحكم به. المتغير الافتراضي dark: يتبع نظام التشغيل بدون أي كود إضافي، وسطر واحد من CSS، وهو @custom-variant dark (&:where(.dark, .dark *));، يقوم بترقية ذلك إلى كلاس (class) يمكنك التحكم به.1 إذا تعطل مفتاح التبديل لديك بعد الترقية، فغالباً ما يكون السبب هو مفتاح إعداد darkMode المفقود، والذي يتم إصلاحه بواسطة نفس السطر المذكور.2 أضف سكريبت <head> المضمن لتحميل الصفحة بدون وميض (flash-free)، ومنطق الحالات الثلاث لمفتاح تبديل حقيقي، وبذلك ستحصل على وضع داكن جاهز للإنتاج يعمل في أي إطار عمل (framework).
بعد ذلك، قم بتثبيت بقية إعدادات v4 الخاصة بك باستخدام دليل Tailwind v4 + Vite المعتمد على CSS أولاً، وإذا كنت جديداً على قواعد التتابع (cascade rules) الأساسية التي تجعل :where() مهماً، فابدأ بـ مقدمة أساسيات CSS.
Footnotes
-
"Dark mode — Core concepts," Tailwind CSS documentation (v4.3), covering the default
prefers-color-schemebehavior, the@custom-variant dark (&:where(.dark, .dark *))override, the data-attribute variant, and the inline-headFOUC script. https://tailwindcss.com/docs/dark-mode ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 -
"Upgrading to Tailwind v4: Missing Defaults, Broken Dark Mode, and Config Issues," tailwindlabs/tailwindcss Discussion #16517 — documents dark-mode breakage after upgrading to v4's CSS-first model, where the
darkModeconfig key no longer applies by default. https://GitHub.com/tailwindlabs/tailwindcss/discussions/16517 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
tailwindcss on npm — version 4.3.1, published 2026-06-12. https://www.npmjs.com/package/tailwindcss ↩ ↩2
-
"[v4] upgrade does not work for dark mode variant with media query," tailwindlabs/tailwindcss Issue #16171 — the automatic upgrade can emit a non-functional
@custom-variant dark (@media not print { .dark & }). https://GitHub.com/tailwindlabs/tailwindcss/issues/16171 ↩ ↩2 -
":where() always has 0 specificity, whereas :is() takes the specificity of its most specific argument." MDN Web Docs,
:where(). https://developer.mozilla.org/en-US/docs/Web/CSS/:where ↩ ↩2 ↩3 ↩4 -
@tailwindcss/vite on npm — version 4.3.1, the official v4 Vite plugin. https://www.npmjs.com/package/@tailwindcss/vite ↩
-
"prefers-color-scheme," MDN Web Docs — a CSS media feature supported across current browsers. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme ↩
-
"Window.matchMedia()," MDN Web Docs — يرجع
MediaQueryListيرسل حدثchangeعندما تتغير حالة المطابقة. https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia ↩ ↩2