دروس تعليمية

التصميم الديناميكي للألوان في React: ما وراء متغيرات CSS

قراءة 2 دقيقة

تتوقف معظم دروس التصميم عند مفتاح الوضع الداكن. تبديل فئة على <html>، قلب حفنة من متغيرات CSS، انتهى. هذا يغطي حالة شائعة لكنه يفوّت المشكلة الأكثر إثارة: ماذا لو تمكّن المستخدمون من اختيار لونهم التجاري؟ ماذا لو خدم منتج SaaS الخاص بك عملاء متعددين، لكل منهم هويته الخاصة؟

هنا يبدأ التصميم الديناميكي للألوان — ومتغيرات CSS وحدها ليست كافية. تحتاج إلى خوارزميات ألوان في وقت التشغيل، وحالة React تبقى عبر التنقل، واستراتيجيات استمرارية، وبنية لا تعيد رسم شجرة مكوناتك بأكملها في كل مرة يتحرك فيها شريط تمرير.


نهج متغيرات CSS لثيمات React

الأساس: بنية الرموز الدلالية

/* globals.css */
:root {
  --bg-base: #F8FAFC;
  --bg-surface: #FFFFFF;
  --bg-sunken: #F1F5F9;

  --text-primary: #1E293B;
  --text-secondary: #64748B;
  --text-disabled: #94A3B8;

  --brand-primary: #2563EB;
  --brand-hover: #1D4ED8;
  --brand-subtle: #EFF6FF;
  --on-brand: #FFFFFF;

  --border-default: #E2E8F0;
  --border-strong: #CBD5E1;

  --status-error: #DC2626;
  --status-success: #16A34A;
  --status-warning: #D97706;
}

حين تحدّث --brand-primary في JavaScript، يتحدث كل مكوّن يشير إليه فوراً.


توليد الثيمات في وقت التشغيل باستخدام خوارزميات الألوان

توليد مجموعة الرموز الكاملة

import chroma from 'chroma-js';

function generateThemeTokens(brandHex) {
  const brand = chroma(brandHex);
  const brandLuminance = brand.luminance();
  const onBrand = brandLuminance > 0.179 ? '#000000' : '#FFFFFF';

  const scale = chroma.scale([
    chroma(brandHex).brighten(2.5).desaturate(0.5).hex(),
    brandHex,
    chroma(brandHex).darken(2.5).hex(),
  ]).mode('oklch').colors(11);

  return {
    '--brand-primary': brandHex,
    '--brand-hover': chroma(brandHex).darken(0.5).hex(),
    '--brand-active': chroma(brandHex).darken(1).hex(),
    '--brand-subtle': scale[1],
    '--on-brand': onBrand,
  };
}

سياق React لإدارة حالة الثيم

بنية ThemeContext

// contexts/ThemeContext.tsx
import {
  createContext,
  useContext,
  useEffect,
  useState,
  useCallback,
  type ReactNode,
} from 'react';
import { generateThemeTokens } from '../lib/theme-generator';

interface ThemeState {
  brandHex: string;
  mode: 'light' | 'dark' | 'system';
}

const ThemeContext = createContext<any>(undefined);
const STORAGE_KEY = 'app-theme';

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [state, setState] = useState<ThemeState>(() => {
    try {
      const stored = localStorage.getItem(STORAGE_KEY);
      if (stored) return JSON.parse(stored) as ThemeState;
    } catch {}
    return { brandHex: '#2563EB', mode: 'system' };
  });

  useEffect(() => {
    const tokens = generateThemeTokens(state.brandHex);
    const root = document.documentElement;
    Object.entries(tokens).forEach(([prop, value]) => {
      root.style.setProperty(prop, value as string);
    });
    localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  }, [state]);

  const setBrand = useCallback((hex: string) => {
    setState(prev => ({ ...prev, brandHex: hex }));
  }, []);

  return (
    <ThemeContext.Provider value={{ ...state, setBrand }}>
      {children}
    </ThemeContext.Provider>
  );
}

فئات الألوان الديناميكية في Tailwind CSS

الحل 1: جسر متغيرات CSS

/* styles.css (Tailwind v4) */
@import "tailwindcss";

@theme {
  --color-brand: var(--brand-primary);
  --color-brand-hover: var(--brand-hover);
  --color-brand-subtle: var(--brand-subtle);
  --color-on-brand: var(--on-brand);
}

الآن bg-brand وtext-brand وhover:bg-brand-hover فئات Tailwind صالحة تعكس قيمة --brand-primary في وقت التشغيل.


منع وميض الثيم الافتراضي عند التحميل

// app/layout.tsx (Next.js App Router)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" suppressHydrationWarning>
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                try {
                  var stored = JSON.parse(localStorage.getItem('app-theme') || '{}');
                  var brand = stored.brandHex || '#2563EB';
                  document.documentElement.style.setProperty('--brand-primary', brand);
                } catch(e) {}
              })();
            `,
          }}
        />
      </head>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

الخلاصة الرئيسية

  • متغيرات CSS المخصصة هي الأساس — الرموز الدلالية المسمّاة بدورها لا بقيمة لونها تتيح تغيير الثيم الكامل دون تعديل المكونات.
  • توليد الرموز في وقت التشغيل من hex لون تجاري واحد يتطلب: مقياساً للإضاءة والظلام، والتحقق من تباين WCAG لنص on-brand، وحساباً منفصلاً لمتغيرات الوضع الداكن.
  • سياق React يدير حالة الثيم ويطبق الرموز على DOM كتأثير جانبي.
  • أضف تأخيراً 150ms للحسابات المكلفة حين ترتبط بمدخلات مستمرة.
  • لـ Tailwind CSS: عرّف جسر متغيرات CSS في @theme حتى تعكس فئات bg-brand قيمة الرمز في وقت التشغيل.
  • استخدم Shade Generator وPalette Generator.

الألوان ذات الصلة

العلامات التجارية ذات الصلة

الأدوات ذات الصلة