AI SaaS Template Docs
AI SaaS Template Docs
AI SaaS Template简介快速开始项目架构
配置管理
项目结构
数据库开发指南
API 开发指南
认证系统
文件管理
支付和账单
自定义主题

自定义主题

本指南涵盖了 AI SaaS Template 中的主题自定义、色彩系统和样式模式。

AI SaaS Template 使用基于 Tailwind CSS v4 和 shadcn/ui 的现代主题系统,支持明暗模式切换和完全自定义的配色方案。该系统采用 CSS 变量和 oklch 色彩空间,确保颜色一致性和无障碍性。

主题架构

src/
├── app/
│   └── globals.css          # 全局样式和 CSS 变量定义
├── components/
│   ├── providers/
│   │   └── theme-provider.tsx # Next.js 主题提供者
│   ├── common/
│   │   └── mode-toggle.tsx    # 主题切换组件
│   └── ui/                    # shadcn/ui 主题化组件
├── lib/
│   └── utils.ts             # 工具函数和 className 合并
└── components.json          # shadcn/ui 配置

主题配置

shadcn/ui 配置

项目使用 shadcn/ui 组件库,配置文件位于 components.json:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "src/app/globals.css",
    "baseColor": "zinc",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui"
  },
  "iconLibrary": "lucide"
}

Tailwind CSS v4 主题配置

项目使用 Tailwind CSS v4 的新语法,在 src/app/globals.css 中定义:

@theme inline {
  --font-sans: var(--font-geist-sans), system-ui, -apple-system,
    BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, "SF Mono",
    Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  
  /* 圆角系统 */
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  
  /* 色彩映射 */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-secondary: var(--secondary);
  /* ... 更多色彩变量 */
  
  /* 动画预设 */
  --animate-accordion-down: accordion-down 0.2s ease-out;
  --animate-accordion-up: accordion-up 0.2s ease-out;
}

主题提供者设置

项目使用 next-themes 库实现主题切换功能:

// src/components/providers/theme-provider.tsx
'use client'

import { ThemeProvider as NextThemesProvider } from 'next-themes'
import type * as React from 'react'

export function ThemeProvider({
  children,
  ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

在应用根布局中使用:

// src/app/layout.tsx
import { ThemeProvider } from '@/components/providers/theme-provider'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

色彩系统

OKLCH 色彩空间

项目使用 OKLCH 色彩空间,它比传统的 HSL 和 RGB 提供更好的感知一致性和渐变效果:

/* src/app/globals.css */
:root {
  --radius: 0.625rem;
  
  /* 浅色主题 */
  --background: oklch(1 0 0);                    /* 纯白背景 */
  --foreground: oklch(0.145 0 0);               /* 深灰文字 */
  --card: oklch(1 0 0);                         /* 卡片背景 */
  --card-foreground: oklch(0.145 0 0);          /* 卡片文字 */
  --primary: oklch(0.205 0 0);                  /* 主色调 */
  --primary-foreground: oklch(0.985 0 0);       /* 主色文字 */
  --secondary: oklch(0.97 0 0);                 /* 辅助色 */
  --secondary-foreground: oklch(0.205 0 0);     /* 辅助色文字 */
  --muted: oklch(0.97 0 0);                     /* 静音色 */
  --muted-foreground: oklch(0.556 0 0);         /* 静音色文字 */
  --destructive: oklch(0.577 0.245 27.325);     /* 危险色 */
  --border: oklch(0.922 0 0);                   /* 边框色 */
  --input: oklch(0.922 0 0);                    /* 输入框边框 */
  --ring: oklch(0.708 0 0);                     /* 焦点环 */
}

.dark {
  /* 暗色主题 */
  --background: oklch(0.145 0 0);               /* 深色背景 */
  --foreground: oklch(0.985 0 0);               /* 浅色文字 */
  --card: oklch(0.205 0 0);                     /* 卡片背景 */
  --card-foreground: oklch(0.985 0 0);          /* 卡片文字 */
  --primary: oklch(0.922 0 0);                  /* 主色调 */
  --primary-foreground: oklch(0.205 0 0);       /* 主色文字 */
  --secondary: oklch(0.269 0 0);                /* 辅助色 */
  --secondary-foreground: oklch(0.985 0 0);     /* 辅助色文字 */
  --muted: oklch(0.269 0 0);                    /* 静音色 */
  --muted-foreground: oklch(0.708 0 0);         /* 静音色文字 */
  --destructive: oklch(0.704 0.191 22.216);     /* 危险色 */
  --border: oklch(1 0 0 / 10%);                 /* 边框色(透明度) */
  --input: oklch(1 0 0 / 15%);                  /* 输入框(透明度) */
  --ring: oklch(0.556 0 0);                     /* 焦点环 */
}

自定义色彩变量

如需添加自定义色彩,可以在 :root 和 .dark 选择器中定义新的 CSS 变量:

/* src/app/globals.css */
:root {
  /* 现有变量... */
  
  /* 自定义色彩变量 */
  --success: oklch(0.641 0.15 142.495);        /* 成功色 */
  --success-foreground: oklch(0.09 0.016 142.495);
  --warning: oklch(0.713 0.18 85.87);          /* 警告色 */
  --warning-foreground: oklch(0.09 0.018 85.87);
  --info: oklch(0.631 0.206 231.604);          /* 信息色 */
  --info-foreground: oklch(0.985 0 0);
  
  /* 图表色彩 */
  --chart-1: oklch(0.646 0.222 41.116);        /* 橙色 */
  --chart-2: oklch(0.6 0.118 184.704);         /* 青色 */
  --chart-3: oklch(0.398 0.07 227.392);        /* 蓝色 */
  --chart-4: oklch(0.828 0.189 84.429);        /* 绿色 */
  --chart-5: oklch(0.769 0.188 70.08);         /* 黄色 */
}

.dark {
  /* 暗色模式覆盖 */
  --success: oklch(0.641 0.15 142.495);
  --warning: oklch(0.713 0.18 85.87);
  --info: oklch(0.631 0.206 231.604);
  
  /* 图表色彩调整 */
  --chart-1: oklch(0.488 0.243 264.376);
  --chart-2: oklch(0.696 0.17 162.48);
  --chart-3: oklch(0.769 0.188 70.08);
  --chart-4: oklch(0.627 0.265 303.9);
  --chart-5: oklch(0.645 0.246 16.439);
}

在组件中使用色彩

// 在组件中使用主题色彩
import { cn } from '@/lib/utils'

function CustomButton({ className, variant = 'default', ...props }) {
  return (
    <button
      className={cn(
        // 基础样式
        'inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors',
        // 根据变体应用不同样式
        {
          'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
          'bg-secondary text-secondary-foreground hover:bg-secondary/80': variant === 'secondary',
          'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
          'border border-input bg-background hover:bg-accent': variant === 'outline',
        },
        className
      )}
      {...props}
    />
  )
}

// 使用自定义色彩
function StatusBadge({ status, children }) {
  return (
    <span
      className={cn(
        'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium',
        {
          'bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400': status === 'success',
          'bg-yellow-50 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400': status === 'warning',
          'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400': status === 'error',
        }
      )}
    >
      {children}
    </span>
  )
}

字体系统

Geist 字体配置

项目使用 Vercel 的 Geist 字体作为默认字体,在 src/app/globals.css 中配置:

@theme inline {
  /* 无衬线字体 */
  --font-sans: var(--font-geist-sans), system-ui, -apple-system,
    BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  
  /* 等宽字体 */
  --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, "SF Mono",
    Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

/* 中文字体优化 */
:lang(zh) {
  font-family: var(--font-geist-sans), "PingFang SC", "Hiragino Sans GB",
    "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
}

字体加载策略

项目使用 Next.js 的字体优化功能来加载 Geist 字体:

// src/app/layout.tsx
import { GeistSans } from 'geist/font/sans'
import { GeistMono } from 'geist/font/mono'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html 
      lang="zh" 
      className={`${GeistSans.variable} ${GeistMono.variable}`}
      suppressHydrationWarning // 防止主题切换时的水合警告
    >
      <body className="font-sans antialiased">
        {children}
      </body>
    </html>
  )
}

字体显示优化

/* src/app/globals.css */
@layer base {
  /* 确保跨平台的一致字体渲染 */
  html {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
  }

  body {
    @apply bg-background text-foreground font-sans;
  }
}

### 自定义字体加载

如果需要使用自定义字体,可以在 `public/fonts` 目录下放置字体文件:

```css
/* src/app/globals.css */
@font-face {
  font-family: 'Custom Font';
  src: url('/fonts/custom-font.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* 优化字体加载体验 */
}

/* 在 Tailwind 中使用自定义字体 */
.font-custom {
  font-family: 'Custom Font', var(--font-sans);
}

在组件中使用:

<h1 className="font-custom text-2xl font-bold">
  使用自定义字体
</h1>

组件样式

UI 组件变体

// src/components/ui/button.tsx
import { cva, type VariantProps } from 'class-variance-authority';

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
  const Comp = asChild ? Slot : 'button';
  return (
    <Comp
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  );
}

自定义组件变体

// 添加自定义变体
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  {
    variants: {
      variant: {
        // ... 现有变体
        gradient: 'bg-gradient-to-r from-primary to-secondary text-primary-foreground',
        glass: 'bg-white/10 backdrop-blur-sm border border-white/20 text-white',
        neon: 'bg-transparent border-2 border-primary text-primary hover:bg-primary hover:text-primary-foreground shadow-[0_0_20px_rgba(59,130,246,0.5)]',
      },
      // ... 其余配置
    },
  }
);

暗色模式自定义

暗色模式样式

/* src/styles/globals.css */
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
}

暗色模式工具

// src/hooks/use-theme.ts
import { useTheme } from 'next-themes';

export function useThemeUtils() {
  const { theme, setTheme, systemTheme } = useTheme();
  
  const isDark = theme === 'dark' || (theme === 'system' && systemTheme === 'dark');
  
  const toggleTheme = () => {
    setTheme(isDark ? 'light' : 'dark');
  };
  
  return {
    theme,
    isDark,
    setTheme,
    toggleTheme,
  };
}

动画系统

动画配置

// src/config/theme.config.ts
export const themeConfig: ThemeConfig = {
  animations: {
    duration: {
      fast: '150ms',
      normal: '300ms',
      slow: '500ms',
    },
    easing: {
      ease: 'ease',
      easeIn: 'ease-in',
      easeOut: 'ease-out',
      easeInOut: 'ease-in-out',
    },
  },
};

自定义动画

/* src/styles/globals.css */
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideIn {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

.animate-fade-in {
  animation: fadeIn 0.3s ease-out;
}

.animate-slide-in {
  animation: slideIn 0.3s ease-out;
}

响应式设计

断点系统

// src/config/theme.config.ts
export const themeConfig: ThemeConfig = {
  breakpoints: {
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
    '2xl': '1536px',
  },
};

响应式组件

// 使用响应式工具
function ResponsiveCard() {
  return (
    <div className="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-4">
      <div className="bg-card rounded-lg p-6 shadow-md">
        <h3 className="text-lg md:text-xl font-semibold mb-2">
          响应式卡片
        </h3>
        <p className="text-sm md:text-base text-muted-foreground">
          这个卡片会适应不同的屏幕尺寸。
        </p>
      </div>
    </div>
  );
}

主题切换

主题切换组件

// src/components/theme-toggle.tsx
'use client';

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';

export function ThemeToggle() {
  const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="icon">
          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">切换主题</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme('light')}>
          浅色
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme('dark')}>
          深色
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme('system')}>
          系统
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

创建自定义主题

添加新的主题变体

如果需要添加完全自定义的主题(比如紫色主题),可以按以下步骤操作:

  1. 在 globals.css 中定义新主题
/* src/app/globals.css */

/* 紫色主题 */
.theme-purple {
  --background: oklch(0.978 0.013 316.8);
  --foreground: oklch(0.145 0 0);
  --card: oklch(0.985 0.007 316.8);
  --card-foreground: oklch(0.145 0 0);
  --primary: oklch(0.67 0.24 310);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.956 0.013 316.8);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.956 0.013 316.8);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.956 0.013 316.8);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0.013 316.8);
  --input: oklch(0.922 0.013 316.8);
  --ring: oklch(0.67 0.24 310);
}

/* 紫色主题的暗色模式 */
.theme-purple.dark {
  --background: oklch(0.145 0.02 316.8);
  --foreground: oklch(0.985 0.007 316.8);
  --card: oklch(0.205 0.02 316.8);
  --card-foreground: oklch(0.985 0.007 316.8);
  --primary: oklch(0.922 0.02 316.8);
  --primary-foreground: oklch(0.205 0.02 316.8);
  /* ... 其他变量 */
}
  1. 更新主题提供者
// src/components/providers/theme-provider.tsx
export function ThemeProvider({
  children,
  ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
  return (
    <NextThemesProvider 
      attribute="class"
      defaultTheme="system"
      enableSystem
      themes={['light', 'dark', 'system', 'purple']} // 添加紫色主题
      {...props}
    >
      {children}
    </NextThemesProvider>
  )
}
  1. 在主题切换组件中添加选项
// 更新 mode-toggle.tsx
<DropdownMenuContent align="end">
  <DropdownMenuItem onClick={() => setTheme('light')}>
    浅色模式
  </DropdownMenuItem>
  <DropdownMenuItem onClick={() => setTheme('dark')}>
    深色模式
  </DropdownMenuItem>
  <DropdownMenuItem onClick={() => setTheme('purple')}>
    紫色主题
  </DropdownMenuItem>
  <DropdownMenuItem onClick={() => setTheme('system')}>
    系统设置
  </DropdownMenuItem>
</DropdownMenuContent>

最佳实践

1. 色彩系统使用

  • 使用语义化命名:始终使用 primary、secondary、muted 等语义化名称
  • OKLCH 色彩空间:使用 OKLCH 可以获得更好的色彩一致性和渐变效果
  • 对比度检查:确保所有色彩组合都符合 WCAG 无障碍标准
  • 两种模式测试:始终在浅色和深色模式下测试所有 UI 组件

2. 性能优化

  • CSS 变量:使用 CSS 变量实现零重绘的主题切换
  • 字体优化:使用 font-display: swap 和 Next.js 字体优化
  • 动画控制:在移动设备上减少不必要的动画效果
  • 懒加载:使用 suppressHydrationWarning 防止主题切换时的闪烁

3. 开发体验

  • TypeScript 支持:使用 shadcn/ui 的完整 TypeScript 支持
  • 组件变体:利用 class-variance-authority 实现类型安全的组件变体
  • 工具链集成:与 Tailwind CSS v4、Biome、Next.js 完美集成
  • 热更新:在开发模式下支持主题的实时预览

4. 维护性和扩展性

  • 集中化配置:所有主题相关配置集中在 globals.css 和 components.json
  • 模块化设计:组件库采用模块化设计,便于独立维护和测试
  • 文档化:为每个自定义主题和组件提供清晰的文档
  • 版本控制:使用语义化版本号管理主题系统的更新

5. 国际化支持

  • 中文字体优化:为中文环境优化字体集和渲染效果
  • RTL 支持:使用 Tailwind CSS 的 RTL 修饰符支持双向文本
  • 数字字体:为不同语言优化数字显示效果

这个现代化的主题系统为 AI SaaS Template 提供了强大的灵活性和可维护性,同时保证了优异的用户体验和开发者体验。

On this page

主题架构主题配置shadcn/ui 配置Tailwind CSS v4 主题配置主题提供者设置色彩系统OKLCH 色彩空间自定义色彩变量在组件中使用色彩字体系统Geist 字体配置字体加载策略字体显示优化组件样式UI 组件变体自定义组件变体暗色模式自定义暗色模式样式暗色模式工具动画系统动画配置自定义动画响应式设计断点系统响应式组件主题切换主题切换组件创建自定义主题添加新的主题变体最佳实践1. 色彩系统使用2. 性能优化3. 开发体验4. 维护性和扩展性5. 国际化支持