Color Theming ใน React: CSS Variables และ Context
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Theming เป็นหนึ่งในปัญหาที่ดูเหมือนง่ายจนกว่าคุณจะต้องดูแลในระดับที่ใหญ่ขึ้น การสลับระหว่างโหมดสว่างและมืดเพียงครั้งเดียวนั้นไม่ยาก แต่ผลิตภัณฑ์ที่บริการหลายแบรนด์ เสนอจานสีที่ผู้ใช้ปรับแต่งได้ หรือต้องสลับธีมทันทีโดยไม่ต้องโหลดหน้าซ้ำ จำเป็นต้องมีสถาปัตยกรรมที่รอบคอบมากขึ้น
โมเดลคอมโพเนนต์ของ React และ CSS custom properties เป็นคู่ที่เหมาะสมตามธรรมชาติสำหรับ theming CSS variables จัดการค่าสีแบบ declarative; React Context จัดการ state ธีม; และ Tailwind CSS (ในโปรเจกต์สมัยใหม่) เชื่อมทั้งสองเข้าด้วยกัน
รูปแบบสถาปัตยกรรมธีม
รูปแบบที่ 1: CSS Variables เท่านั้น (ไม่มี JavaScript State)
รูปแบบที่ง่ายที่สุดสำหรับการรองรับ dark mode พื้นฐานไม่ต้องการ state ของ React เลย media query prefers-color-scheme เปลี่ยนค่า custom property และทุกคอมโพเนนต์อัปเดตโดยอัตโนมัติ:
/* globals.css */
:root {
--color-bg: #F8FAFC;
--color-text: #1E293B;
--color-brand: #2563EB;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0F172A;
--color-text: #F1F5F9;
--color-brand: #60A5FA;
}
}
คอมโพเนนต์ใช้ var(--color-bg) และไม่จำเป็นต้องรู้เกี่ยวกับธีมปัจจุบัน เบราว์เซอร์จัดการทุกอย่าง
เมื่อใดควรใช้: ไซต์ที่คุณต้องการเคารพการตั้งค่าระดับ OS และไม่ต้องการ toggle ที่ผู้ใช้ควบคุม
ข้อจำกัด: ผู้ใช้ไม่มีทางแทนที่การตั้งค่า OS ของตนในแอป
รูปแบบที่ 2: Data Attribute Theme Switching
เพิ่มแอตทริบิวต์ data-theme บนองค์ประกอบ <html> เพื่อทำให้ธีมที่ใช้งานอยู่ชัดเจนและแทนที่ได้ รูปแบบนี้เข้ากันได้กับ SSR หลีกเลี่ยง flash ของธีมที่ผิด และรวมกับการคงอยู่ของ localStorage ได้อย่างง่ายดาย:
[data-theme="light"],
:root {
--color-bg: #F8FAFC;
--color-text: #1E293B;
--color-brand: #2563EB;
--color-surface: #FFFFFF;
--color-border: #E2E8F0;
}
[data-theme="dark"] {
--color-bg: #0F172A;
--color-text: #F1F5F9;
--color-brand: #60A5FA;
--color-surface: #1E293B;
--color-border: #334155;
}
เมื่อใดควรใช้: แอปการผลิตส่วนใหญ่ที่ต้องการ toggle ที่ผู้ใช้ควบคุม รูปแบบ data attribute เป็นมาตรฐานอุตสาหกรรมปัจจุบัน — ใช้โดย Tailwind CSS dark mode, Radix UI และระบบดีไซน์สมัยใหม่ส่วนใหญ่
รูปแบบที่ 3: Class-Based Theme Switching
คล้ายกับ data-attribute แต่ใช้ CSS class การกำหนดค่า darkMode: 'class' ของ Tailwind อาศัยรูปแบบนี้:
.dark {
--color-bg: #0F172A;
--color-text: #F1F5F9;
}
เมื่อใดควรใช้: โปรเจกต์ที่ใช้ Tailwind CSS พร้อมกลยุทธ์ dark mode แบบ class
CSS Variables สำหรับสี: ระบบ Token
การตั้งชื่อแบบ Semantic แทนแบบ Descriptive
การตัดสินใจที่สำคัญที่สุดในระบบ token คือการตั้งชื่อ การตั้งชื่อตามค่าสี (--blue-500, --gray-900) ทำให้ตัวแปรเข้าใจง่ายแต่ไม่สามารถทำธีมได้ การตั้งชื่อตามบทบาท semantic (--color-brand, --color-text-muted) ช่วยให้ค่าเปลี่ยนแปลงได้อย่างสมบูรณ์ในแต่ละธีมในขณะที่คอมโพเนนต์ยังคงถูกต้อง:
:root {
/* ---- Color Primitives (ไม่ใช้โดยตรงในคอมโพเนนต์) ---- */
--blue-500: #3B82F6;
--blue-700: #1D4ED8;
--slate-50: #F8FAFC;
--slate-800: #1E293B;
--slate-900: #0F172A;
/* ---- Semantic Tokens (ใช้ในคอมโพเนนต์) ---- */
--text-primary: var(--slate-800);
--text-secondary: #64748B;
--bg-base: var(--slate-50);
--brand: var(--blue-700);
--brand-hover: #1E40AF;
--error: #DC2626;
--success: #16A34A;
--warning: #D97706;
}
[data-theme="dark"] {
--text-primary: #F1F5F9;
--text-secondary: #94A3B8;
--bg-base: #0F172A;
--brand: #60A5FA;
--brand-hover: #93C5FD;
--error: #F87171;
--success: #4ADE80;
--warning: #FCD34D;
}
ระบบสองชั้น (primitives + semantic tokens) ให้ความยืดหยุ่นธีมอย่างสมบูรณ์พร้อมทั้งรักษาคอมโพเนนต์ให้สะอาด
การสร้างสเกลสีของคุณ
ก่อนสร้างระบบ token คุณต้องมีจานสีดิบ Shade Generator สร้างสเกล 50–950 ที่สมบูรณ์จากสีแบรนด์เดียว — รูปแบบสเกลเดียวกับที่ Tailwind CSS ใช้
React Context สำหรับ Theme State
รูปแบบ ThemeContext
สำหรับแอปที่มี theme toggle ที่ผู้ใช้ควบคุม React Context มอบชั้นการจัดการ state Context เก็บชื่อธีมที่ใช้งานอยู่และเปิดเผยฟังก์ชัน toggle:
// contexts/ThemeContext.tsx
import { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
interface ThemeContextValue {
theme: Theme;
resolvedTheme: 'light' | 'dark';
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window === 'undefined') return 'system';
return (localStorage.getItem('theme') as Theme) || 'system';
});
const resolvedTheme: 'light' | 'dark' =
theme === 'system'
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: theme;
useEffect(() => {
document.documentElement.setAttribute('data-theme', resolvedTheme);
localStorage.setItem('theme', theme);
}, [theme, resolvedTheme]);
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme: setThemeState }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
การป้องกัน Flash of Wrong Theme (FOTWT)
แอปที่ render ฝั่งเซิร์ฟเวอร์เผชิญกับปัญหา flash ของธีมที่ผิด วิธีแก้คือ blocking inline script ใน <head> ที่อ่าน localStorage และใช้แอตทริบิวต์ธีมก่อนที่หน้าจะ render:
<script>
(function() {
try {
var stored = localStorage.getItem('theme');
var systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored === 'dark' || stored === 'light'
? stored
: (systemDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch(e) {}
})();
</script>
Tailwind CSS Themes
Tailwind v3: Class-Based Dark Mode
ใน Tailwind v3 dark mode ต้องตั้ง darkMode: 'class' ใน tailwind.config.js ซึ่งทำให้ Tailwind ใช้ utility dark: เฉพาะเมื่อ class .dark อยู่บนองค์ประกอบ <html>:
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
brand: {
50: '#EFF6FF',
500: '#3B82F6',
600: '#2563EB',
700: '#1D4ED8',
},
},
},
},
};
Tailwind v4: CSS-First Configuration
Tailwind v4 ย้ายการกำหนดค่าไปยัง CSS ทั้งหมด โดยใช้ CSS custom properties แบบ native:
@import "tailwindcss";
@theme {
--color-brand-500: #3B82F6;
--color-brand-600: #2563EB;
--color-brand-700: #1D4ED8;
}
กลยุทธ์ Multi-Brand Theming
ความท้าทาย
แพลตฟอร์ม SaaS ที่บริการลูกค้าหลายราย ผลิตภัณฑ์ white-label หรือระบบดีไซน์ที่ใช้ร่วมกันในหลายแบรนด์ผลิตภัณฑ์จำเป็นต้องจัดการไม่เพียงแค่ตัวแปรมืด/สว่างแต่ยังรวมถึงเอกลักษณ์สีที่แตกต่างกันอย่างสิ้นเชิง
Brand Tokens เป็น CSS Variable Overrides
แนวทางที่ดูแลได้ง่ายที่สุดกำหนด semantic tokens ที่ไม่ขึ้นกับแบรนด์ใน base stylesheet จากนั้น override การกำหนดค่า primitive ต่อแบรนด์:
:root {
--brand-primary-raw: 37 99 235;
--brand-primary: rgb(var(--brand-primary-raw));
}
[data-brand="acme"] {
--brand-primary-raw: 37 99 235; /* #2563EB */
}
[data-brand="globex"] {
--brand-primary-raw: 124 58 237; /* #7C3AED */
}
[data-brand="initech"] {
--brand-primary-raw: 22 163 74; /* #16A34A */
}
ใช้ Shade Generator เพื่อสร้างสเกลสีสมบูรณ์สำหรับแต่ละแบรนด์
ประเด็นสำคัญ
- CSS custom properties คือรากฐาน: กำหนด semantic tokens (
--text-primary,--brand-primary) และเปลี่ยนค่าของพวกมันต่อธีม แทนที่จะใช้ class แบบ conditional ในทุกคอมโพเนนต์ - Data-attribute theme switching (
data-theme="dark") เป็นรูปแบบที่ยืดหยุ่นที่สุด — แทนที่ได้ด้วย JavaScript เข้ากันได้กับ SSR และรวมกับ media queryprefers-color-schemeได้ - ระบบ token สองชั้น (สเกลสี primitive + semantic role tokens) ให้ความยืดหยุ่นธีมอย่างสมบูรณ์พร้อมทั้งรักษาคอมโพเนนต์ให้สะอาด
- ใช้ Shade Generator เพื่อสร้างสเกลสีสมบูรณ์ 50–950 จากสีหลักของแบรนด์
- React Context จัดการ state ธีม (ชื่อธีมที่ใช้งาน) และ side effects (ตั้งค่า data attribute, บันทึกลง localStorage)
- ป้องกัน flash ของธีมที่ผิด ด้วย blocking inline script ใน
<head>ที่อ่านlocalStorageและใช้แอตทริบิวต์ธีมก่อนที่เบราว์เซอร์จะ render พิกเซลใดๆ - การรวม Tailwind CSS: v3 ใช้
darkMode: 'class'พร้อม prefixdark:; v4 ใช้การกำหนดค่า@themeแบบ CSS-first - Multi-brand theming ซ้อน data attribute อื่น (
data-brand) บนระบบธีม override ค่า primitive token ต่อแบรนด์ในขณะที่ semantic tokens และคอมโพเนนต์ไม่เปลี่ยนแปลง