扩展默认主题 
VitePress 默认的主题已经针对文档进行了优化,并且可以进行定制。请参考默认主题配置概览获取完整的选项列表。
但是有一些情况仅靠配置是不够的。例如:
- 你需要调整 CSS 样式;
 - 你需要修改 Vue 应用实例,例如注册全局组件;
 - 你需要通过 layout 插槽将自定义内容注入到主题中;
 
这些高级自定义配置将需要使用自定义主题来“拓展”默认主题。
提示
在继续之前,请确保首先阅读自定义主题以了解其工作原理。
自定义 CSS 
可以通过覆盖根级别的 CSS 变量来自定义默认主题的 CSS:
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import './custom.css'
export default DefaultTheme// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import './custom.css'
export default DefaultTheme2
3
4
5
/* .vitepress/theme/custom.css */
:root {
	--vp-c-brand-1: #646cff;
	--vp-c-brand-2: #747bff;
}/* .vitepress/theme/custom.css */
:root {
	--vp-c-brand-1: #646cff;
	--vp-c-brand-2: #747bff;
}2
3
4
5
查看默认主题 CSS 变量来获取可以被覆盖的变量。
使用自定义字体 
VitePress 使用 Inter 作为默认字体,并且将其包含在生成的输出中。该字体在生产环境中也会自动预加载。但是如果你要使用不同的主字体,这可能不是一个好的选择。
为了避免在生成后的输出中包含 Inter 字体,请从 vitepress/theme-without-fonts 中导入主题:
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme-without-fonts'
import './my-fonts.css'
export default DefaultTheme// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme-without-fonts'
import './my-fonts.css'
export default DefaultTheme2
3
4
5
/* .vitepress/theme/custom.css */
:root {
  --vp-font-family-base: /* normal text font */
  --vp-font-family-mono: /* code font */
}/* .vitepress/theme/custom.css */
:root {
  --vp-font-family-base: /* normal text font */
  --vp-font-family-mono: /* code font */
}2
3
4
5
警告
如果你在使用像是团队页这样的组件,请确保也在从 vitepress/theme-without-fonts 中导入它们!
如果你的字体是通过 @font-face 引用的本地文件,它将会被作为资源被包含在 .vitepress/dist/asset 目录下,并且使用哈希后的文件名。为了预加载这个文件,请使用 transformHead 构建钩子:
// .vitepress/config.js
export default {
	transformHead({ assets }) {
		// adjust the regex accordingly to match your font
		const myFontFile = assets.find((file) => /font-name\.\w+\.woff2/)
		if (myFontFile) {
			return [
				[
					'link',
					{
						rel: 'preload',
						href: myFontFile,
						as: 'font',
						type: 'font/woff2',
						crossorigin: '',
					},
				],
			]
		}
	},
}// .vitepress/config.js
export default {
	transformHead({ assets }) {
		// adjust the regex accordingly to match your font
		const myFontFile = assets.find((file) => /font-name\.\w+\.woff2/)
		if (myFontFile) {
			return [
				[
					'link',
					{
						rel: 'preload',
						href: myFontFile,
						as: 'font',
						type: 'font/woff2',
						crossorigin: '',
					},
				],
			]
		}
	},
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注册全局组件 
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
/** @type {import('vitepress').Theme} */
export default {
	extends: DefaultTheme,
	enhanceApp({ app }) {
		// register your custom global components
		app.component('MyGlobalComponent' /* ... */)
	},
}// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
/** @type {import('vitepress').Theme} */
export default {
	extends: DefaultTheme,
	enhanceApp({ app }) {
		// register your custom global components
		app.component('MyGlobalComponent' /* ... */)
	},
}2
3
4
5
6
7
8
9
10
11
如果你使用 TypeScript:
// .vitepress/theme/index.ts
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
export default {
	extends: DefaultTheme,
	async enhanceApp({ app }) {
		// register your custom global components
		app.component('MyGlobalComponent' /* ... */)
	},
} satisfies Theme// .vitepress/theme/index.ts
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
export default {
	extends: DefaultTheme,
	async enhanceApp({ app }) {
		// register your custom global components
		app.component('MyGlobalComponent' /* ... */)
	},
} satisfies Theme2
3
4
5
6
7
8
9
10
11
因为我们使用 Vite,你还可以利用 Vite 的 glob 导入功能来自动注册一个组件目录。
布局插槽 
默认主题的 <Layout/> 组件有一些插槽,能够被用来在页面的特定位置注入内容。下面这个例子展示了将一个组件注入到大纲之前:
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'
export default {
	extends: DefaultTheme,
	// override the Layout with a wrapper component that
	// injects the slots
	Layout: MyLayout,
}// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'
export default {
	extends: DefaultTheme,
	// override the Layout with a wrapper component that
	// injects the slots
	Layout: MyLayout,
}2
3
4
5
6
7
8
9
10
<!--.vitepress/theme/MyLayout.vue-->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
	<Layout>
		<template #aside-outline-before> My custom sidebar top content </template>
	</Layout>
</template><!--.vitepress/theme/MyLayout.vue-->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
	<Layout>
		<template #aside-outline-before> My custom sidebar top content </template>
	</Layout>
</template>2
3
4
5
6
7
8
9
10
11
12
你也可以使用渲染函数。
// .vitepress/theme/index.js
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
export default {
	extends: DefaultTheme,
	Layout() {
		return h(DefaultTheme.Layout, null, {
			'aside-outline-before': () => h(MyComponent),
		})
	},
}// .vitepress/theme/index.js
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyComponent from './MyComponent.vue'
export default {
	extends: DefaultTheme,
	Layout() {
		return h(DefaultTheme.Layout, null, {
			'aside-outline-before': () => h(MyComponent),
		})
	},
}2
3
4
5
6
7
8
9
10
11
12
13
默认主题布局的全部可用插槽如下:
- 当 
layout: 'doc'(默认) 在 frontmatter 中被启用时:doc-topdoc-bottomdoc-footer-beforedoc-beforedoc-aftersidebar-nav-beforesidebar-nav-afteraside-topaside-bottomaside-outline-beforeaside-outline-afteraside-ads-beforeaside-ads-after
 - 当 
layout: 'home'在 frontmatter 中被启用时:home-hero-beforehome-hero-infohome-hero-imagehome-hero-afterhome-features-beforehome-features-after
 - 当 
layout: 'page'在 frontmatter 中被启用时:page-toppage-bottom
 - 当未找到页面 (404) 时: 
not-found
 - 总是启用: 
layout-toplayout-bottomnav-bar-title-beforenav-bar-title-afternav-bar-content-beforenav-bar-content-afternav-screen-content-beforenav-screen-content-after
 
关于外观切换 
你可以扩展默认主题以在切换颜色模式时提供自定义过渡动画。一个例子:
<!-- .vitepress/theme/Layout.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { nextTick, provide } from 'vue'
const { isDark } = useData()
const enableTransitions = () => 'startViewTransition' in document && window.matchMedia('(prefers-reduced-motion: no-preference)').matches
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
	if (!enableTransitions()) {
		isDark.value = !isDark.value
		return
	}
	const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`]
	await document.startViewTransition(async () => {
		isDark.value = !isDark.value
		await nextTick()
	}).ready
	document.documentElement.animate(
		{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
		{
			duration: 300,
			easing: 'ease-in',
			pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
		},
	)
})
</script>
<template>
	<DefaultTheme.Layout />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
	animation: none;
	mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
	z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
	z-index: 9999;
}
.VPSwitchAppearance {
	width: 22px !important;
}
.VPSwitchAppearance .check {
	transform: none !important;
}
</style><!-- .vitepress/theme/Layout.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { nextTick, provide } from 'vue'
const { isDark } = useData()
const enableTransitions = () => 'startViewTransition' in document && window.matchMedia('(prefers-reduced-motion: no-preference)').matches
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
	if (!enableTransitions()) {
		isDark.value = !isDark.value
		return
	}
	const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`]
	await document.startViewTransition(async () => {
		isDark.value = !isDark.value
		await nextTick()
	}).ready
	document.documentElement.animate(
		{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
		{
			duration: 300,
			easing: 'ease-in',
			pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
		},
	)
})
</script>
<template>
	<DefaultTheme.Layout />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
	animation: none;
	mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
	z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
	z-index: 9999;
}
.VPSwitchAppearance {
	width: 22px !important;
}
.VPSwitchAppearance .check {
	transform: none !important;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
结果(谨慎使用!:闪烁的颜色、突然的移动、高亮度):
Demo

有关视图过渡动画的更多详细信息,请参阅 Chrome 文档。
路由切换时 
即将到来。
重写内部组件 
你可以使用 Vite 的 aliases 来用你的自定义组件替换默认主题的组件:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitepress'
export default defineConfig({
	vite: {
		resolve: {
			alias: [
				{
					find: /^.*\/VPNavBar\.vue$/,
					replacement: fileURLToPath(new URL('./components/CustomNavBar.vue', import.meta.url)),
				},
			],
		},
	},
})import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitepress'
export default defineConfig({
	vite: {
		resolve: {
			alias: [
				{
					find: /^.*\/VPNavBar\.vue$/,
					replacement: fileURLToPath(new URL('./components/CustomNavBar.vue', import.meta.url)),
				},
			],
		},
	},
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
想要了解组件的确切名称请参考我们的源代码。因为组件是内部的,因此在小版本更迭中,它们名字改动的可能性很小。