跳至主要内容

如何自訂主題

本指南說明如何自訂應用程式的視覺主題。

前置條件

已了解 主題系統 的基本概念。

切換預設主題

AppFuse Web 內建多種主題,可直接使用:

// src/App.tsx
function App() {
return (
<div data-theme="garden">
{/* 應用程式內容 */}
</div>
);
}

可用的內建主題:fusion(預設)、lightdarkgardencupcakevalentine 等。

建立自訂主題

步驟 1:定義主題顏色

/* src/tailwind.css */
@import "tailwindcss";
@plugin "daisyui";

@layer base {
[data-theme="my-brand"] {
/* 主要顏色 */
--p: 220 90% 56%; /* primary */
--pf: 220 90% 46%; /* primary-focus */
--pc: 0 0% 100%; /* primary-content */

/* 次要顏色 */
--s: 280 60% 60%; /* secondary */
--sf: 280 60% 50%; /* secondary-focus */
--sc: 0 0% 100%; /* secondary-content */

/* 強調顏色 */
--a: 160 60% 45%; /* accent */
--af: 160 60% 35%; /* accent-focus */
--ac: 0 0% 100%; /* accent-content */

/* 中性顏色 */
--n: 220 15% 20%; /* neutral */
--nf: 220 15% 15%; /* neutral-focus */
--nc: 0 0% 100%; /* neutral-content */

/* 基底顏色 */
--b1: 0 0% 100%; /* base-100 */
--b2: 220 15% 96%; /* base-200 */
--b3: 220 15% 92%; /* base-300 */
--bc: 220 15% 20%; /* base-content */

/* 狀態顏色 */
--in: 200 90% 50%; /* info */
--su: 160 60% 45%; /* success */
--wa: 40 90% 50%; /* warning */
--er: 0 80% 60%; /* error */

/* 圓角 */
--rounded-box: 0.5rem;
--rounded-btn: 0.25rem;
--rounded-badge: 1rem;
}
}

步驟 2:應用主題

// src/App.tsx
function App() {
return (
<div data-theme="my-brand">
{/* 應用程式內容 */}
</div>
);
}

動態切換主題

步驟 1:建立 Theme Context

// src/contexts/ThemeContext.tsx
import { createContext, useContext, useState, useEffect } from 'react';

type Theme = 'light' | 'dark' | 'my-brand';

interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}

const ThemeContext = createContext<ThemeContextType | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
return (localStorage.getItem('theme') as Theme) || 'light';
});

useEffect(() => {
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);

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

export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}

步驟 2:建立切換器

// src/components/ThemeSwitcher.tsx
import { useTheme } from '@/contexts/ThemeContext';

const themes = [
{ value: 'light', label: '淺色' },
{ value: 'dark', label: '深色' },
{ value: 'my-brand', label: '品牌' },
];

export function ThemeSwitcher() {
const { theme, setTheme } = useTheme();

return (
<select value={theme} onChange={(e) => setTheme(e.target.value as any)}>
{themes.map((t) => (
<option key={t.value} value={t.value}>
{t.label}
</option>
))}
</select>
);
}

自訂元件樣式

覆寫特定元件

/* src/tailwind.css */
@layer components {
/* 自訂按鈕樣式 */
.btn-primary {
@apply bg-gradient-to-r from-blue-500 to-purple-500;
@apply hover:from-blue-600 hover:to-purple-600;
}

/* 自訂輸入框樣式 */
.input {
@apply border-2 focus:border-primary;
}

/* 自訂卡片樣式 */
.card {
@apply shadow-lg hover:shadow-xl transition-shadow;
}
}

使用 CSS 變數

/* 定義變數 */
:root {
--header-height: 64px;
--sidebar-width: 240px;
--content-max-width: 1200px;
}

/* 使用變數 */
.header {
height: var(--header-height);
}

.sidebar {
width: var(--sidebar-width);
}

響應式主題

根據系統偏好

// src/contexts/ThemeContext.tsx
const getSystemTheme = (): 'light' | 'dark' => {
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};

// 監聽系統主題變化
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => {
if (theme === 'system') {
setTheme(e.matches ? 'dark' : 'light');
}
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [theme]);

下一步