扩展默认主题
VitePress 默认的主题已经针对文档进行了优化,并且可以进行自定义。请参考默认主题配置概览获取完整的选项列表。
但是有些情况仅靠配置是不够的。例如:
- 需要调整 CSS 样式;
- 需要全站可用的自定义组件;
- 需要通过自定义 Layout 把内容注入到主题的特定位置。
这些高级自定义需要使用自定义主题来“扩展”默认主题。
提示
在继续之前,请确保首先阅读自定义主题以了解其工作原理。
自定义 CSS
默认主题的样式以 CSS 变量 为主。在主题入口导入自定义 css 并覆盖变量即可:
import Theme from '@10coding/vitepress-react/theme'
import './custom.css'
export default Theme/* .vitepress-react/theme/custom.css */
:root {
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #747bff;
}使用自定义字体
默认主题使用 Inter 作为默认字体并打包进产物。如果不想打包 Inter,请从 @10coding/vitepress-react/theme-without-fonts 导入主题:
import Theme from '@10coding/vitepress-react/theme-without-fonts'
import './my-fonts.css'
export default Theme/* .vitepress-react/theme/my-fonts.css */
:root {
--vp-font-family-base: /* 普通文本字体 */
--vp-font-family-mono: /* 代码字体 */
}警告
如果使用诸如团队页这类组件,也请从 @10coding/vitepress-react/theme-without-fonts 导入它们。
若字体是本地 @font-face 文件,它会被当作资源放进 .vitepress-react/dist/assets(带哈希文件名)。需要预加载时,使用 transformHead 构建钩子:
export default {
transformHead({ assets }) {
// 相应地调整正则表达式以匹配字体
const myFontFile = assets.find(file => /font-name\.[\w-]+\.woff2/.test(file))
if (myFontFile) {
return [
[
'link',
{
rel: 'preload',
href: myFontFile,
as: 'font',
type: 'font/woff2',
crossorigin: ''
}
]
]
}
}
}全站可用的组件
本项目 是 React,没有 Vue 的 app.component 全局注册机制(EnhanceAppContext 里的 registerComponent 为未来预留,当前不会渲染到 md 页面)。可用方案:
- 页面级导入(推荐):在用到该组件的每个 md 页面的
<script>顶层import,正文用大写标签(见在 Markdown 中使用 React)。默认主题导出的组件(VPBadge、VPTeamMembers、VPTeamPage等)也按此导入,或在 markdown 里直接用@10coding/vitepress-react/theme自动导入的标签名。 - Layout 插槽:若组件需要出现在“每个页面”的固定位置(例如全站横幅、大纲上方卡片),用下一节的 Layout 具名插槽。
- 内部组件覆盖:想替换默认主题某个内部组件(如
VPNavBar)本身,用内部组件覆盖的Theme.components注册表。
Layout 具名插槽
Vue 默认主题的 <Layout/> 提供具名插槽(如 <template #aside-outline-before>);React 实现 用等价的具名 props(统一 camelCase)挂在 DefaultTheme.Layout 上,支持两种值形态:
ReactNode:静态节点(等价于 Vue 的模板内容);(ctx) => ReactNode:渲染函数(插槽可带参数;当前各挂载点无额外数据,ctx为空对象,后续扩展时调用处不变)。
接线方式:在自定义主题的 Layout 里包一层默认 Layout,把插槽作为 props 传入(主题对象用 defineTheme 定义以获得类型约束,见自定义主题):
import { defineTheme } from '@10coding/vitepress-react'
import Theme from '@10coding/vitepress-react/theme'
import { MyLayout } from './MyLayout.tsx'
export default defineTheme({
...Theme,
Layout: MyLayout
})import { Layout } from '@10coding/vitepress-react/theme'
/** 大纲上方注入的内容(等价 Vue 的 #aside-outline-before) */
function MyOutlineTop() {
return <div className="outline-tip">My custom sidebar top content</div>
}
export function MyLayout() {
return (
<Layout
asideOutlineBefore={<MyOutlineTop />}
navBarContentAfter={() => <a href="https://github.com">GitHub</a>}
/>
)
}也支持把多个插槽放进 slots 表(直传 prop 与 slots 同名时,直传 prop 优先):
<Layout slots={{ docFooterBefore: <ShareButtons />, layoutBottom: <FooterNote /> }} />插槽挂载点(全部可选,未提供时渲染零变化):
| React prop(camelCase) | Vue 插槽名 | 挂载位置 |
|---|---|---|
layoutTop / layoutBottom | layout-top / layout-bottom | .Layout 根首/末(全站最外层) |
navBarTitleBefore / navBarTitleAfter | nav-bar-title-before / nav-bar-title-after | 顶栏站点标题链接前后 |
navBarContentBefore / navBarContentAfter | nav-bar-content-before / nav-bar-content-after | 顶栏 content-body 前后 |
navScreenContentBefore / navScreenContentAfter | nav-screen-content-before / nav-screen-content-after | 移动端全屏导航容器前后 |
sidebarNavBefore / sidebarNavAfter | sidebar-nav-before / sidebar-nav-after | 侧栏 <nav> 前后 |
docBefore / docAfter | doc-before / doc-after | 文档正文 .doc 根首/末 |
docTop / docBottom | doc-top / doc-bottom | 正文 .content-container 首/末 |
docFooterBefore | doc-footer-before | 文档页脚(<VPDocFooter/>)前 |
asideTop / asideBottom | aside-top / aside-bottom | 右侧栏根首/末 |
asideOutlineBefore / asideOutlineAfter | aside-outline-before / aside-outline-after | 右侧栏大纲前后 |
asideAdsBefore / asideAdsAfter | aside-ads-before / aside-ads-after | 右侧栏广告区前后(仅在 themeConfig.carbonAds 存在时渲染) |
- 插槽内容渲染在默认主题既有 DOM 容器内部(如
asideOutlineBefore与大纲同处.asideContent滚动区),不套新外壳、不破坏布局;需要留白时由自定义节点自带 class/margin。 - 需要按页面条件注入(如只对
layout: 'home'显示)时,在自定义Layout里用useData()的frontmatter分支后再把插槽传给<Layout …/>。 - 插槽注入点是固定的;想替换某个内部组件本身,用下一节的内部组件覆盖。
重写内部组件
Vue 版用 Vite alias 替换 VPNavBar.vue 等内部组件;React 实现 以编译产物发布、内部都是相对路径 import,alias 无法稳定命中,因此提供等价的主题级组件注册表Theme.components——把“按内部组件名覆盖”移到渲染期解析(用 defineTheme 包一层可让 components 的 key 受 THEME_COMPONENT_NAMES 约束,拼错组件名会立即报错):
import { defineTheme } from '@10coding/vitepress-react'
import Theme from '@10coding/vitepress-react/theme'
import { MyNavBar } from './MyNavBar.tsx'
export default defineTheme({
extends: Theme,
components: {
VPNavBar: MyNavBar, // 替换整个顶栏;其余内部组件保持默认
VPSidebarItem: MySidebarItem // 叶子组件同样可覆盖
}
})要点:
- 注册表开放到叶子:
components的 key 覆盖默认主题内部组合树的全部VP*组件(VPNav/VPNavBar/VPNavMenuLink/VPSidebarItem/VPIcon等,完整名单见导出的THEME_COMPONENT_NAMES)。内部组合组件渲染子组件前经useThemeComponent(name, fallback)解析:命中注册表用你的实现,否则用默认——不传components时渲染与打包零变化。 - 与
extends叠加:components按 key 合并(子主题只覆盖自己列出的名字,extends链上其它覆盖保留)。 - 替换组件的 props 契约:覆盖的组件必须接受默认内部组件被调用时的同名 props(半公开契约,与 Vue 上游内部组件同性质);需要数据时用
useData/useLayout等公开 hook 即可。 - 不在覆盖范围:markdown 自动注入的
VPBadge/VPTeam*走静态 import,不受注册表影响;想换掉它们请直接在 md 页面的<script>里导入你自己的组件(见在 Markdown 中使用 React)。 - 取舍顺序:只想加一块内容用上一节插槽;想替换某个内部组件的行为/结构用注册表;想改变整体骨架(如去掉侧栏自己排)则自定义/自绘
Layout(见自定义主题)。
内部组件仍是实现细节,即便上游也可能在小版本中改名或调整 props;覆盖层数越深,升级成本越高。请优先使用公开配置项 → Layout 插槽 → 注册表覆盖 → 自绘 Layout 的优先级顺序。