Mục lục

Module 3: Màu sắc & Theming

Tạo color palette có khả năng theming (light/dark mode) với semantic color tokens và CSS variables.

Thời lượng: 1 tuần
Level: Beginner
Yêu cầu: Modules 1-2 hoàn thành

Tổng quan Module

Module này dạy bạn cách thiết kế color system chuyên nghiệp với khả năng theming. Bạn sẽ học cách tạo color palette, semantic tokens, và implement dark mode.

Kết quả học tập

Sau module này, bạn sẽ:

  • Thiết kế color palette cân đối
  • Tạo semantic color tokens
  • Implement light/dark mode switching
  • Đảm bảo accessibility với color contrast

Bài 3.1: Thiết kế Color Palette

Lý thuyết

Color Scale:

Một color scale tốt cần 9-11 shades từ sáng nhất đến tối nhất:

css:
/* Gray scale (9 shades) */
--gray-50: #F9FAFB;   /* Lightest - backgrounds */
--gray-100: #F3F4F6;
--gray-200: #E5E7EB;  
--gray-300: #D1D5DB;  /* Borders */
--gray-400: #9CA3AF;
--gray-500: #6B7280;  /* Placeholder text */
--gray-600: #4B5563;  /* Secondary text */
--gray-700: #374151;
--gray-800: #1F2937;  /* Primary text */
--gray-900: #111827;  /* Darkest - headings */

/* Brand color (Blue) */
--blue-50: #EFF6FF;
--blue-100: #DBEAFE;
--blue-200: #BFDBFE;
--blue-300: #93C5FD;
--blue-400: #60A5FA;
--blue-500: #3B82F6;  /* Base brand color */
--blue-600: #2563EB;  /* Hover state */
--blue-700: #1D4ED8;
--blue-800: #1E40AF;
--blue-900: #1E3A8A;

Quy tắc chọn màu:

  1. Base (500): Màu chính, dễ nhìn
  2. Hover (600): Tối hơn 10-15%
  3. Active (700): Tối hơn 20-25%
  4. Light (100-200): Cho backgrounds
  5. Dark (800-900): Cho text on light bg

Nguyên tắc then chốt

"Palette tốt = easy to use. Nếu designer phải suy nghĩ lâu để chọn shade 300 hay 400, palette đã thất bại."

Thực hành

Bài tập: Tạo color palette cho brand

Steps:

  1. Chọn Base Color (500):
css:
--primary-500: #3B82F6; /* Your brand color */
  1. Generate Scale (dùng tool):
  1. Test Contrast (WCAG AA):
css:
/* Text on background phải đạt tỷ lệ 4.5:1 */
--text-on-primary: white; /* Check contrast với --primary-500 */
  1. Create Semantic Tokens:
css:
:root {
  /* Brand */
  --color-primary: var(--blue-500);
  --color-primary-hover: var(--blue-600);
  
  /* Status */
  --color-success: var(--green-500);
  --color-warning: var(--yellow-500);
  --color-danger: var(--red-500);
  --color-info: var(--blue-500);
  
  /* Neutral */
  --color-text-primary: var(--gray-900);
  --color-text-secondary: var(--gray-600);
  --color-text-tertiary: var(--gray-500);
  --color-text-disabled: var(--gray-400);
  
  --color-bg-primary: white;
  --color-bg-secondary: var(--gray-50);
  --color-bg-tertiary: var(--gray-100);
  
  --color-border: var(--gray-300);
  --color-border-hover: var(--gray-400);
}

Nhiệm vụ:

  • Generate 3 color scales: Gray, Brand, Accent
  • Define semantic tokens
  • Test contrast ratios (4.5:1 for text)
  • Document usage guidelines

Câu hỏi thảo luận

  1. Palette Size: 9 shades đủ hay cần 11? Trade-offs?
  2. Naming: --blue-500 vs --primary vs --brand-blue?
  3. Accessibility: Làm sao đảm bảo mọi color combo đều WCAG AA?

Bài 3.2: Semantic Color Tokens

Lý thuyết

3 Layers của Color Tokens:

Layer 1: Primitive (Raw values)

css:
--blue-500: #3B82F6;
--red-500: #EF4444;

Layer 2: Semantic (Meaning-based)

css:
--color-primary: var(--blue-500);
--color-danger: var(--red-500);

Layer 3: Component (Specific usage)

css:
--button-bg-primary: var(--color-primary);
--alert-bg-danger: var(--red-50);
--alert-text-danger: var(--red-700);

Tại sao cần Semantic Tokens?

  • Theming: Swap light/dark mode
  • Rebranding: Change --color-primary → entire site updates
  • Consistency: Components dùng --color-primary, không phải --blue-500

Nguyên tắc then chốt

"Components KHÔNG BAO GIỜ reference primitive tokens trực tiếp. Luôn qua semantic layer."

Thực hành

Bài tập: Thiết kế semantic token structure

Text Colors:

css:
:root {
  /* Primary text - for headings, important content */
  --color-text-primary: var(--gray-900);
  
  /* Secondary text - for descriptions, meta info */
  --color-text-secondary: var(--gray-600);
  
  /* Tertiary text - for labels, captions */
  --color-text-tertiary: var(--gray-500);
  
  /* Disabled text */
  --color-text-disabled: var(--gray-400);
  
  /* Inverted text (on dark backgrounds) */
  --color-text-inverse: white;
  
  /* Text on primary button */
  --color-text-on-primary: white;
}

Background Colors:

css:
:root {
  /* Canvas - main page background */
  --color-bg-canvas: white;
  
  /* Primary - cards, panels */
  --color-bg-primary: white;
  
  /* Secondary - hover states, subtle backgrounds */
  --color-bg-secondary: var(--gray-50);
  
  /* Tertiary - nested backgrounds */
  --color-bg-tertiary: var(--gray-100);
  
  /* Overlay - modal backdrops */
  --color-bg-overlay: rgba(0, 0, 0, 0.5);
}

Interactive Colors:

css:
:root {
  /* Links */
  --color-link: var(--blue-600);
  --color-link-hover: var(--blue-700);
  --color-link-visited: var(--purple-600);
  
  /* Focus ring */
  --color-focus: var(--blue-500);
  
  /* Selection */
  --color-selection-bg: var(--blue-100);
  --color-selection-text: var(--blue-900);
}

Nhiệm vụ:

  • Define 20+ semantic color tokens
  • Group by category: text, bg, border, interactive
  • Document when to use each token
  • Create example components using tokens

Câu hỏi thảo luận

  1. Granularity: Có cần --color-text-link riêng hay dùng --color-primary?
  2. Naming: --color-bg-primary vs --bg-color-primary vs --primary-bg-color?
  3. Component Tokens: Khi nào tạo token riêng cho component (vd: --button-bg-primary)?

Bài 3.3: Dark Mode Implementation

Lý thuyết

Dark Mode Strategy:

Option 1: CSS Variables (Recommended)

css:
/* Light mode (default) */
:root {
  --color-text-primary: var(--gray-900);
  --color-bg-primary: white;
}

/* Dark mode */
[data-theme="dark"] {
  --color-text-primary: var(--gray-50);
  --color-bg-primary: var(--gray-900);
}

Option 2: Media Query

css:
:root {
  --color-text-primary: var(--gray-900);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-text-primary: var(--gray-50);
  }
}

Option 3: Class-based

css:
.light {
  --color-text-primary: var(--gray-900);
}

.dark {
  --color-text-primary: var(--gray-50);
}

Nguyên tắc then chốt

"Dark mode không phải 'invert colors'. Phải thiết kế lại cho readable, không chói mắt."

Thực hành

Bài tập: Implement dark mode toggle

1. Setup Tokens:

css:
:root {
  /* Light mode */
  --color-text-primary: var(--gray-900);
  --color-text-secondary: var(--gray-600);
  --color-bg-primary: white;
  --color-bg-secondary: var(--gray-50);
  --color-border: var(--gray-300);
}

[data-theme="dark"] {
  /* Dark mode - adjust for readability */
  --color-text-primary: var(--gray-50);
  --color-text-secondary: var(--gray-400);
  --color-bg-primary: var(--gray-900);
  --color-bg-secondary: var(--gray-800);
  --color-border: var(--gray-700);
  
  /* Brand colors may need adjustment */
  --color-primary: var(--blue-400); /* Lighter for dark bg */
}

2. React Toggle Component:

tsx:
'use client';

import { useEffect, useState } from 'react';
import { Moon, Sun } from 'lucide-react';

export function ThemeToggle() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  useEffect(() => {
    // Load from localStorage
    const saved = localStorage.getItem('theme') as 'light' | 'dark' | null;
    const preferred = saved || (
      window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
    );
    setTheme(preferred);
    document.documentElement.setAttribute('data-theme', preferred);
  }, []);
  
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
  };
  
  return (
    <button
      onClick={toggleTheme}
      className="theme-toggle"
      aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
    >
      {theme === 'light' ? <Moon size={20} /> : <Sun size={20} />}
    </button>
  );
}

3. Dark Mode Best Practices:

  • Reduce Contrast: Dark bg không nên pure black (#000), dùng #111827
  • Adjust Colors: Primary colors có thể cần lighter shade trong dark mode
  • Test Readability: WCAG contrast vẫn phải đạt trong dark mode
  • Persist Choice: Save preference to localStorage
  • Respect System: Default to prefers-color-scheme

Nhiệm vụ:

  • Implement theme toggle
  • Define dark mode token values
  • Test contrast ratios in both modes
  • Add smooth transitions

Câu hỏi thảo luận

  1. Default: Default light hay dark? Hay follows system?
  2. Images: Làm sao handle images trong dark mode? Invert? Replace?
  3. Shadows: Shadows trong dark mode có cần adjust không?

Tổng kết Module

Bạn đã học:

  • Thiết kế color palette với 9-11 shades
  • Tạo semantic color tokens (text, bg, border, interactive)
  • Implement dark mode với CSS variables

Bước tiếp theo

Module 4 sẽ dạy về Component Architecture - thiết kế API, composition patterns, và compound components.

Tiếp tục Module 4 →


Tài liệu tham khảo

Quảng cáo
mdhorizontal