Skip to content

自定义主题

Rspress 提供强大的主题定制功能,允许您创建符合品牌形象的独特文档站点。本章探讨如何自定义主题样式、创建自定义主题以及调整布局。

主题系统概述

默认主题

Rspress 附带一个功能完整的默认主题:

  • 响应式布局
  • 深色/浅色模式
  • 可自定义的配色方案
  • 灵活的组件系统
  • 可访问性功能

自定义选项

主题可以通过以下方式自定义:

  1. CSS 变量: 修改颜色、字体和间距
  2. 主题配置: 在配置文件中设置选项
  3. 自定义组件: 覆盖默认组件
  4. 完全自定义主题: 从头创建新主题

使用 CSS 变量

基本自定义

在自定义 CSS 文件中覆盖 CSS 变量:

css
/* styles/custom.css */
:root {
  /* 主色 */
  --rp-c-brand: #667eea;
  --rp-c-brand-dark: #5568d3;
  --rp-c-brand-light: #8b9dff;

  /* 背景色 */
  --rp-c-bg: #ffffff;
  --rp-c-bg-soft: #f9f9f9;
  --rp-c-bg-mute: #f1f1f1;

  /* 文本色 */
  --rp-c-text-1: #213547;
  --rp-c-text-2: #476582;
  --rp-c-text-3: #7d8590;

  /* 边框色 */
  --rp-c-divider: #e2e8f0;
  --rp-c-border: #c9d3df;
}

rspress.config.ts 中导入:

typescript
import { defineConfig } from 'rspress/config';

export default defineConfig({
  // ... 其他配置
  builderConfig: {
    html: {
      tags: [
        {
          tag: 'link',
          attrs: {
            rel: 'stylesheet',
            href: '/styles/custom.css'
          }
        }
      ]
    }
  }
});

深色模式定制

自定义深色模式颜色:

css
/* 深色模式变量 */
.dark {
  /* 主色(深色模式) */
  --rp-c-brand: #8b9dff;
  --rp-c-brand-dark: #667eea;
  --rp-c-brand-light: #a0aeff;

  /* 背景色(深色模式) */
  --rp-c-bg: #1a1a1a;
  --rp-c-bg-soft: #252525;
  --rp-c-bg-mute: #2f2f2f;

  /* 文本色(深色模式) */
  --rp-c-text-1: #f0f0f0;
  --rp-c-text-2: #c9d1d9;
  --rp-c-text-3: #8b949e;

  /* 边框色(深色模式) */
  --rp-c-divider: #30363d;
  --rp-c-border: #3d444d;
}

排版定制

自定义字体和间距:

css
:root {
  /* 字体 */
  --rp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --rp-font-family-mono: 'Fira Code', 'Courier New', monospace;

  /* 字号 */
  --rp-font-size-root: 16px;
  --rp-font-size-xs: 12px;
  --rp-font-size-sm: 14px;
  --rp-font-size-md: 16px;
  --rp-font-size-lg: 18px;
  --rp-font-size-xl: 20px;

  /* 行高 */
  --rp-line-height-tight: 1.25;
  --rp-line-height-base: 1.5;
  --rp-line-height-relaxed: 1.75;

  /* 间距 */
  --rp-space-1: 4px;
  --rp-space-2: 8px;
  --rp-space-3: 12px;
  --rp-space-4: 16px;
  --rp-space-5: 20px;
  --rp-space-6: 24px;
  --rp-space-8: 32px;
}

主题配置

配置导航栏

自定义导航栏外观:

typescript
import { defineConfig } from 'rspress/config';

export default defineConfig({
  title: '我的文档',
  themeConfig: {
    // 导航栏配置
    nav: [
      { text: '首页', link: '/' },
      { text: '指南', link: '/guide/' },
      { text: '关于', link: '/about' }
    ],

    // Logo
    logo: '/logo.png',
    logoText: '我的项目',

    // 社交链接
    socialLinks: [
      {
        icon: 'github',
        link: 'https://github.com/username/project'
      },
      {
        icon: 'twitter',
        link: 'https://twitter.com/username'
      },
      {
        icon: 'discord',
        link: 'https://discord.gg/invite'
      }
    ],

    // 导航栏外观
    appearance: true, // 启用深色模式切换
  }
});

配置侧边栏

自定义侧边栏样式:

typescript
export default defineConfig({
  themeConfig: {
    sidebar: {
      '/guide/': [
        {
          text: '入门',
          items: [
            { text: '介绍', link: '/guide/introduction' },
            { text: '快速开始', link: '/guide/quick-start' }
          ]
        }
      ]
    },

    // 侧边栏设置
    outlineTitle: '本页目录',
    outline: [2, 3], // 显示 h2 和 h3 标题
  }
});

配置页脚

添加自定义页脚:

typescript
export default defineConfig({
  themeConfig: {
    footer: {
      message: '使用 MIT 许可证发布',
      copyright: 'Copyright © 2024 我的项目'
    },

    // 上一页/下一页链接
    docFooter: {
      prev: '上一页',
      next: '下一页'
    },

    // 编辑链接
    editLink: {
      pattern: 'https://github.com/username/project/edit/main/docs/:path',
      text: '在 GitHub 上编辑此页'
    },

    // 最后更新时间
    lastUpdatedText: '最后更新'
  }
});

组件自定义

覆盖默认组件

创建自定义组件以覆盖默认组件:

tsx
// theme/components/CustomNavbar.tsx
import React from 'react';

export function CustomNavbar() {
  return (
    <nav className="custom-navbar">
      <div className="navbar-brand">
        <img src="/logo.png" alt="Logo" />
        <span>我的项目</span>
      </div>
      <div className="navbar-links">
        <a href="/guide">指南</a>
        <a href="/api">API</a>
        <a href="/blog">博客</a>
      </div>
      <div className="navbar-actions">
        <button className="search-button">搜索</button>
        <button className="theme-toggle">🌙</button>
      </div>
    </nav>
  );
}
css
/* theme/components/CustomNavbar.css */
.custom-navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background: var(--rp-c-bg);
  border-bottom: 1px solid var(--rp-c-divider);
}

.navbar-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.25rem;
  font-weight: bold;
}

.navbar-brand img {
  width: 32px;
  height: 32px;
}

.navbar-links {
  display: flex;
  gap: 2rem;
}

.navbar-links a {
  color: var(--rp-c-text-1);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.3s;
}

.navbar-links a:hover {
  color: var(--rp-c-brand);
}

.navbar-actions {
  display: flex;
  gap: 1rem;
}

.search-button,
.theme-toggle {
  padding: 0.5rem 1rem;
  border: 1px solid var(--rp-c-border);
  background: var(--rp-c-bg);
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s;
}

.search-button:hover,
.theme-toggle:hover {
  background: var(--rp-c-bg-soft);
  border-color: var(--rp-c-brand);
}

自定义首页

创建自定义首页组件:

tsx
// theme/components/CustomHome.tsx
import React from 'react';
import './CustomHome.css';

export function CustomHome() {
  return (
    <div className="custom-home">
      {/* 主要部分 */}
      <section className="hero">
        <h1 className="hero-title">
          <span className="gradient-text">下一代</span>
          <br />
          文档框架
        </h1>
        <p className="hero-subtitle">
          快速、现代且强大的文档解决方案
        </p>
        <div className="hero-actions">
          <a href="/guide/quick-start" className="btn btn-primary">
            开始使用
          </a>
          <a href="/guide/introduction" className="btn btn-secondary">
            了解更多
          </a>
        </div>
      </section>

      {/* 功能部分 */}
      <section className="features">
        <div className="container">
          <div className="feature-grid">
            <div className="feature-card">
              <div className="feature-icon">⚡</div>
              <h3>闪电般快速</h3>
              <p>使用 Vite 构建,实现极快的开发和构建时间</p>
            </div>
            <div className="feature-card">
              <div className="feature-icon">🎨</div>
              <h3>完全可定制</h3>
              <p>完全控制主题和样式</p>
            </div>
            <div className="feature-card">
              <div className="feature-icon">📱</div>
              <h3>响应式</h3>
              <p>在所有设备上完美运行</p>
            </div>
            <div className="feature-card">
              <div className="feature-icon">🔍</div>
              <h3>强大搜索</h3>
              <p>内置全文搜索功能</p>
            </div>
            <div className="feature-card">
              <div className="feature-icon">🌍</div>
              <h3>国际化</h3>
              <p>内置多语言支持</p>
            </div>
            <div className="feature-card">
              <div className="feature-icon">⚙️</div>
              <h3>可扩展</h3>
              <p>通过插件和主题扩展功能</p>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}
css
/* theme/components/CustomHome.css */
.custom-home {
  width: 100%;
}

.hero {
  text-align: center;
  padding: 120px 20px;
  background: linear-gradient(135deg,
    rgba(102, 126, 234, 0.1) 0%,
    rgba(118, 75, 162, 0.1) 100%);
}

.hero-title {
  font-size: 4rem;
  font-weight: 800;
  margin-bottom: 1rem;
  line-height: 1.2;
}

.gradient-text {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.hero-subtitle {
  font-size: 1.5rem;
  color: var(--rp-c-text-2);
  margin-bottom: 2rem;
}

.hero-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.btn {
  padding: 12px 32px;
  border-radius: 8px;
  text-decoration: none;
  font-weight: 600;
  font-size: 1rem;
  transition: all 0.3s;
  display: inline-block;
}

.btn-primary {
  background: var(--rp-c-brand);
  color: white;
  border: 2px solid var(--rp-c-brand);
}

.btn-primary:hover {
  background: var(--rp-c-brand-dark);
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}

.btn-secondary {
  background: transparent;
  color: var(--rp-c-brand);
  border: 2px solid var(--rp-c-brand);
}

.btn-secondary:hover {
  background: var(--rp-c-brand);
  color: white;
}

.features {
  padding: 80px 20px;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
}

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 30px;
}

.feature-card {
  padding: 30px;
  border: 1px solid var(--rp-c-divider);
  border-radius: 12px;
  transition: all 0.3s;
}

.feature-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  border-color: var(--rp-c-brand);
}

.feature-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.feature-card h3 {
  font-size: 1.25rem;
  margin-bottom: 0.5rem;
  color: var(--rp-c-text-1);
}

.feature-card p {
  color: var(--rp-c-text-2);
  line-height: 1.6;
}

@media (max-width: 768px) {
  .hero-title {
    font-size: 2.5rem;
  }

  .hero-subtitle {
    font-size: 1.25rem;
  }

  .hero-actions {
    flex-direction: column;
    align-items: center;
  }

  .btn {
    width: 100%;
    max-width: 300px;
  }

  .feature-grid {
    grid-template-columns: 1fr;
  }
}

创建自定义主题

主题结构

创建完整的自定义主题:

theme/
├── index.ts                 # 主题入口
├── components/
│   ├── Layout.tsx          # 主布局
│   ├── Navbar.tsx          # 导航栏
│   ├── Sidebar.tsx         # 侧边栏
│   ├── Content.tsx         # 内容区域
│   └── Footer.tsx          # 页脚
├── styles/
│   ├── vars.css            # CSS 变量
│   ├── base.css            # 基础样式
│   └── components.css      # 组件样式
└── hooks/
    ├── useTheme.ts         # 主题钩子
    └── useNav.ts           # 导航钩子

主题入口

typescript
// theme/index.ts
import { Theme } from 'rspress/theme';
import { Layout } from './components/Layout';
import './styles/vars.css';
import './styles/base.css';
import './styles/components.css';

const theme: Theme = {
  Layout,
  // 注册其他组件...
};

export default theme;

export * from './components';
export * from './hooks';

自定义布局组件

tsx
// theme/components/Layout.tsx
import React from 'react';
import { usePageData } from 'rspress/runtime';
import { Navbar } from './Navbar';
import { Sidebar } from './Sidebar';
import { Content } from './Content';
import { Footer } from './Footer';

export function Layout() {
  const pageData = usePageData();
  const { frontmatter } = pageData;

  // 检查是否为自定义页面
  if (frontmatter.pageType === 'custom') {
    return <>{pageData.content}</>;
  }

  return (
    <div className="theme-layout">
      <Navbar />
      <div className="layout-container">
        {frontmatter.sidebar !== false && <Sidebar />}
        <Content />
      </div>
      <Footer />
    </div>
  );
}

主题钩子

typescript
// theme/hooks/useTheme.ts
import { useState, useEffect } from 'react';

export function useTheme() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    // 从 localStorage 读取主题
    const savedTheme = localStorage.getItem('theme') as 'light' | 'dark';
    if (savedTheme) {
      setTheme(savedTheme);
      document.documentElement.classList.toggle('dark', savedTheme === 'dark');
    }
  }, []);

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.classList.toggle('dark', newTheme === 'dark');
  };

  return { theme, toggleTheme };
}

布局自定义

响应式布局

创建响应式布局系统:

css
/* theme/styles/layout.css */
.theme-layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.layout-container {
  display: flex;
  flex: 1;
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
  padding: 0 24px;
}

/* 侧边栏 */
.sidebar {
  width: 280px;
  flex-shrink: 0;
  padding: 24px 0;
  position: sticky;
  top: 64px;
  height: calc(100vh - 64px);
  overflow-y: auto;
}

/* 内容 */
.content {
  flex: 1;
  min-width: 0;
  padding: 24px 48px;
}

/* 目录 */
.outline {
  width: 240px;
  flex-shrink: 0;
  padding: 24px 0;
  position: sticky;
  top: 64px;
  height: calc(100vh - 64px);
  overflow-y: auto;
}

/* 响应式 */
@media (max-width: 1200px) {
  .outline {
    display: none;
  }
}

@media (max-width: 960px) {
  .sidebar {
    position: fixed;
    left: 0;
    top: 64px;
    transform: translateX(-100%);
    transition: transform 0.3s;
    background: var(--rp-c-bg);
    z-index: 100;
    box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
  }

  .sidebar.open {
    transform: translateX(0);
  }

  .layout-container {
    padding: 0 16px;
  }

  .content {
    padding: 24px 0;
  }
}

网格系统

实现灵活的网格系统:

css
/* theme/styles/grid.css */
.grid {
  display: grid;
  gap: var(--grid-gap, 24px);
}

.grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }

/* 自动填充 */
.grid-auto-fit {
  grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 300px), 1fr));
}

.grid-auto-fill {
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-min, 300px), 1fr));
}

/* 响应式 */
@media (max-width: 768px) {
  .grid-cols-2,
  .grid-cols-3,
  .grid-cols-4 {
    grid-template-columns: 1fr;
  }
}

高级主题功能

动画和过渡

添加流畅的动画:

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

@keyframes slideIn {
  from {
    transform: translateX(-20px);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

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

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

/* 页面过渡 */
.page-transition-enter {
  opacity: 0;
  transform: translateY(20px);
}

.page-transition-enter-active {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.3s, transform 0.3s;
}

.page-transition-exit {
  opacity: 1;
}

.page-transition-exit-active {
  opacity: 0;
  transition: opacity 0.2s;
}

自定义滚动条

样式化滚动条:

css
/* theme/styles/scrollbar.css */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--rp-c-bg);
}

::-webkit-scrollbar-thumb {
  background: var(--rp-c-border);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--rp-c-brand);
}

/* Firefox */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--rp-c-border) var(--rp-c-bg);
}

打印样式

优化打印体验:

css
/* theme/styles/print.css */
@media print {
  /* 隐藏导航元素 */
  .navbar,
  .sidebar,
  .outline,
  .footer,
  .edit-link {
    display: none !important;
  }

  /* 全宽内容 */
  .content {
    max-width: 100%;
    padding: 0;
  }

  /* 优化排版 */
  body {
    font-size: 12pt;
    line-height: 1.5;
    color: #000;
    background: #fff;
  }

  /* 保持链接可见 */
  a[href]:after {
    content: " (" attr(href) ")";
    font-size: 10pt;
    color: #666;
  }

  /* 分页控制 */
  h1, h2, h3 {
    page-break-after: avoid;
  }

  pre, blockquote {
    page-break-inside: avoid;
  }
}

最佳实践

性能优化

  1. CSS 优化
css
/* 使用 CSS 自定义属性 */
:root {
  --primary-color: #667eea;
}

/* 避免复杂选择器 */
.simple-class { }  /* 好 */
div > ul > li > a { }  /* 避免 */

/* 使用 transform 而不是 position */
.animated {
  transform: translateX(10px);  /* 好 */
  /* left: 10px; */  /* 避免 */
}
  1. 代码分割
typescript
// theme/index.ts
import { lazy } from 'react';

const HeavyComponent = lazy(() => import('./components/HeavyComponent'));

可访问性

确保主题可访问:

css
/* 焦点样式 */
:focus-visible {
  outline: 2px solid var(--rp-c-brand);
  outline-offset: 2px;
}

/* 跳过链接 */
.skip-to-content {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--rp-c-brand);
  color: white;
  padding: 8px 16px;
  text-decoration: none;
  z-index: 100;
}

.skip-to-content:focus {
  top: 0;
}

主题测试

测试主题组件:

typescript
// theme/__tests__/Layout.test.tsx
import { render, screen } from '@testing-library/react';
import { Layout } from '../components/Layout';

describe('Layout', () => {
  it('renders navbar', () => {
    render(<Layout />);
    expect(screen.getByRole('navigation')).toBeInTheDocument();
  });

  it('renders content area', () => {
    render(<Layout />);
    expect(screen.getByRole('main')).toBeInTheDocument();
  });
});

故障排除

常见问题

  1. 样式未应用
typescript
// 确保在配置中导入 CSS
export default defineConfig({
  builderConfig: {
    html: {
      tags: [
        {
          tag: 'link',
          attrs: {
            rel: 'stylesheet',
            href: '/styles/custom.css'
          }
        }
      ]
    }
  }
});
  1. 深色模式不工作
css
/* 确保使用正确的类名 */
.dark {
  /* 深色模式样式 */
}

/* 或使用数据属性 */
[data-theme="dark"] {
  /* 深色模式样式 */
}
  1. 组件未覆盖
typescript
// 确保导出组件
export { CustomNavbar as Navbar } from './components/CustomNavbar';

下一步


最佳实践

  • 使用 CSS 变量以提高可维护性
  • 确保深色模式和浅色模式都经过测试
  • 优化移动设备体验
  • 遵循可访问性指南
  • 记录自定义主题组件

性能

  • 避免过度的 CSS 选择器
  • 优化图片和资源
  • 使用代码分割处理大型组件
  • 最小化重绘和重排