Published on

Hệ thống Theme: Từ Matrix đến Tím Cyber

Authors

Việc tạo một hệ thống theme cho Git Command Terminal phức tạp hơn là chỉ thay đổi vài màu sắc. Mình muốn người dùng có thể chuyển đổi giữa các phong cách thẩm mỹ hoàn toàn khác biệt - từ màu xanh lá Matrix đậm chất cyberpunk đến màu tím Cyber huyền bí.

Đây là cách mình đã xây dựng một hệ thống theme mạnh mẽ, không chỉ dừng lại ở việc đổi màu cơ bản.

Không Chỉ Là Màu Sắc

Một theme terminal cần truyền tải được cá tính và tâm trạng:

  • Matrix: Phong cách hacker với màu xanh lá sáng trên nền đen
  • Oceanic: Những sắc xanh dương dịu mát gợi cảm giác sâu thẳm của đại dương
  • Sunset: Các tông màu cam và vàng ấm áp tạo cảm giác ấm cúng
  • Midnight: Các sắc tím đậm cho những đêm code muộn
  • Neon: Sự kết hợp giữa hồng và xanh cyan theo phong cách cyberpunk
  • Light: Theme sáng truyền thống để dễ tiếp cận

Mỗi theme cần phải tạo cảm giác thống nhất trên toàn bộ ứng dụng, chứ không chỉ riêng khu vực terminal.

Yêu Cầu Hệ Thống Thiết Kế

Hệ thống theme cần hỗ trợ:

  • Chuyển đổi theme lúc chạy mà không cần tải lại trang
  • Mối quan hệ màu sắc nhất quán giữa các theme
  • Các yếu tố về khả năng tiếp cận (tỷ lệ tương phản)
  • Dễ dàng thêm theme mới
  • Đảm bảo an toàn kiểu (type safety) cho các giá trị của theme

Tại Sao Chọn OKLCH Thay Vì HSL/RGB

Các không gian màu truyền thống có những hạn chế:

/* HSL - độ sáng cảm nhận không đồng đều */
hsl(240, 50%, 50%) /* Không có cùng độ sáng cảm nhận như */
hsl(60, 50%, 50%)  /* màu vàng này */

/* RGB - không có mối quan hệ trực quan */
rgb(128, 128, 255) /* Khó tạo ra các biến thể hài hòa */

OKLCH (Oklch Lightness Chroma Hue) mang lại sự đồng nhất về mặt cảm nhận:

/* OKLCH - độ sáng cảm nhận đồng đều */
oklch(0.7 0.15 240) /* Cùng độ sáng cảm nhận như */
oklch(0.7 0.15 60)  /* màu này, chỉ khác tông màu (hue) */

Lợi Ích Khi Tạo Theme

Độ Sáng Dễ Đoán: Độ sáng 0.7 trông như nhau ở mọi tông màu Điều Chỉnh Trực Quan: Chỉ cần thay đổi tông màu (hue) để tạo ra các biến thể màu sắc Gradient Tốt Hơn: Chuyển màu mượt mà hơn Khả Năng Tiếp Cận: Dễ dàng duy trì tỷ lệ tương phản

Bảng Màu Toàn Diện

Mỗi theme định nghĩa tất cả các màu sắc theo ngữ nghĩa:

export interface Theme {
  id: string
  name: string
  colors: {
    background: string
    foreground: string
    card: string
    cardForeground: string
    popover: string
    popoverForeground: string
    primary: string
    primaryForeground: string
    secondary: string
    secondaryForeground: string
    accent: string
    accentForeground: string
    destructive: string
    destructiveForeground: string
    muted: string
    mutedForeground: string
    border: string
    input: string
    ring: string
  }
}

Ví Dụ Về Theme Matrix

Theme Matrix kinh điển sử dụng OKLCH:

{
  id: 'matrix',
  name: 'Matrix Terminal',
  colors: {
    background: 'oklch(0.15 0.02 240)',      // Màu đen-xanh dương rất tối
    foreground: 'oklch(0.7 0.15 145)',       // Màu xanh lá matrix sáng
    primary: 'oklch(0.7 0.15 145)',          // Cùng màu xanh lá matrix
    primaryForeground: 'oklch(0.15 0.02 240)', // Nền tối
    accent: 'oklch(0.7 0.15 45)',            // Màu vàng-xanh lá cảnh báo
    destructive: 'oklch(0.65 0.2 25)',       // Màu đỏ-cam báo lỗi
    // ... các màu khác
  }
}

Áp Dụng Theme Động

Việc chuyển đổi theme lúc chạy sẽ cập nhật các biến CSS:

export const applyTheme = (theme: Theme) => {
  const root = document.documentElement

  Object.entries(theme.colors).forEach(([key, value]) => {
    // Chuyển đổi camelCase sang kebab-case
    const cssVariable = key.replace(/([A-Z])/g, '-$1').toLowerCase()
    root.style.setProperty(`--${cssVariable}`, value)
  })
}

Lưu Trữ Theme

Lựa chọn theme của người dùng được lưu lại giữa các phiên làm việc:

export const getCurrentTheme = (): string => {
  if (typeof window === 'undefined') return 'matrix'
  return localStorage.getItem('git-terminal-theme') || 'matrix'
}

export const setCurrentTheme = (themeId: string) => {
  if (typeof window === 'undefined') return
  localStorage.setItem('git-terminal-theme', themeId)
}

Hook Xử Lý Theme

Một custom hook quản lý trạng thái và việc lưu trữ theme:

export function useThemeHandlers({ currentThemeId, setCurrentThemeId }: UseThemeHandlersProps) {
  useEffect(() => {
    const savedTheme = getCurrentTheme()
    setCurrentThemeId(savedTheme)
    const theme = themes.find((t) => t.id === savedTheme) || themes[0]
    applyTheme(theme)
  })

  useEffect(() => {
    const theme = themes.find((t) => t.id === currentThemeId) || themes[0]
    applyTheme(theme)
  }, [currentThemeId])

  const handleThemeChange = (themeId: string) => {
    setCurrentThemeId(themeId)
    setCurrentTheme(themeId)
    const theme = themes.find((t) => t.id === themeId)
    if (theme) {
      toast.success(strings.buildSuccessMessage('themeChanged', { themeName: theme.name }))
    }
  }

  return {
    handleThemeChange,
  }
}

Lý Thuyết Màu Sắc Trong Thực Tế

Mỗi theme sử dụng các nguyên tắc của lý thuyết màu sắc:

  • Theme Matrix: Đơn sắc xanh lá với độ tương phản cao
  • Theme Oceanic: Các màu tương đồng (analogous) như xanh dương và xanh mòng két
  • Theme Sunset: Các tông màu nóng như cam và vàng
  • Theme Midnight: Các tông màu lạnh như tím và hồng cánh sen
  • Theme Neon: Các màu bổ sung (complementary) như hồng và xanh cyan

Looking Forward

Việc xây dựng hệ thống theme này đã dạy mình rằng một theme tốt không chỉ là tập hợp các màu sắc - chúng là những ngôn ngữ thiết kế gắn kết, ảnh hưởng đến toàn bộ trải nghiệm người dùng. Việc đầu tư vào không gian màu OKLCH và các vai trò màu sắc theo ngữ nghĩa đã mang lại một hệ thống vừa mạnh mẽ cho lập trình viên, vừa thú vị cho người dùng.

Hệ thống theme hoàn chỉnh, với tất cả sáu theme và cơ sở hạ tầng hỗ trợ, có sẵn tại kho mã nguồn Git Command Terminal. Nó cho thấy cách thiết kế màu sắc có chủ đích có thể biến một terminal đơn giản thành một trải nghiệm cá nhân hóa và đắm chìm.