963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
6.1 KiB
6.1 KiB
name, description
| name | description |
|---|---|
| extending-vitepress-default-theme | Customize CSS variables, use layout slots, register global components, and override theme fonts |
Extending Default Theme
Customize the default theme through CSS, slots, and Vue components.
Theme Entry File
Create .vitepress/theme/index.ts to extend the default theme:
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import './custom.css';
export default DefaultTheme;
CSS Variables
Override root CSS variables:
/* .vitepress/theme/custom.css */
:root {
/* Brand colors */
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #747bff;
--vp-c-brand-3: #9499ff;
/* Backgrounds */
--vp-c-bg: #ffffff;
--vp-c-bg-soft: #f6f6f7;
/* Text */
--vp-c-text-1: #213547;
--vp-c-text-2: #476582;
}
.dark {
--vp-c-brand-1: #747bff;
--vp-c-bg: #1a1a1a;
}
See all CSS variables.
Home Hero Customization
:root {
/* Gradient name color */
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(120deg, #bd34fe, #41d1ff);
/* Hero image glow */
--vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
--vp-home-hero-image-filter: blur(44px);
}
Custom Fonts
Remove Inter font and use your own:
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme-without-fonts';
import './fonts.css';
export default DefaultTheme;
/* .vitepress/theme/fonts.css */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
}
:root {
--vp-font-family-base: 'MyFont', sans-serif;
--vp-font-family-mono: 'Fira Code', monospace;
}
Preload fonts in config:
// .vitepress/config.ts
export default {
transformHead({ assets }) {
const fontFile = assets.find((file) => /myfont\.[\w-]+\.woff2/.test(file));
if (fontFile) {
return [
[
'link',
{ rel: 'preload', href: fontFile, as: 'font', type: 'font/woff2', crossorigin: '' },
],
];
}
},
};
Global Components
Register components available in all markdown:
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import MyComponent from './components/MyComponent.vue';
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('MyComponent', MyComponent);
},
};
Use in markdown:
<MyComponent :prop="value" />
Layout Slots
Inject content into specific locations:
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import MyLayout from './MyLayout.vue';
export default {
extends: DefaultTheme,
Layout: MyLayout,
};
<!-- .vitepress/theme/MyLayout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme';
const { Layout } = DefaultTheme;
</script>
<template>
<Layout>
<template #aside-outline-before>
<div>Above outline</div>
</template>
<template #doc-before>
<div>Before doc content</div>
</template>
<template #doc-after>
<div>After doc content</div>
</template>
</Layout>
</template>
Available Slots
Doc layout (layout: doc):
doc-top,doc-bottomdoc-before,doc-afterdoc-footer-beforesidebar-nav-before,sidebar-nav-afteraside-top,aside-bottomaside-outline-before,aside-outline-afteraside-ads-before,aside-ads-after
Home layout (layout: home):
home-hero-before,home-hero-afterhome-hero-info-before,home-hero-info,home-hero-info-afterhome-hero-actions-after,home-hero-imagehome-features-before,home-features-after
Page layout (layout: page):
page-top,page-bottom
Always available:
layout-top,layout-bottomnav-bar-title-before,nav-bar-title-afternav-bar-content-before,nav-bar-content-afternot-found(404 page)
Using Render Functions
Alternative to template slots:
// .vitepress/theme/index.ts
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),
});
},
};
Override Internal Components
Replace default theme components with Vite aliases:
// .vitepress/config.ts
import { fileURLToPath, URL } from 'node:url';
export default {
vite: {
resolve: {
alias: [
{
find: /^.*\/VPNavBar\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/CustomNavBar.vue', import.meta.url),
),
},
],
},
},
};
View Transitions
Custom dark mode toggle animation:
<!-- .vitepress/theme/Layout.vue -->
<script setup>
import { useData } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import { nextTick, provide } from 'vue';
const { isDark } = useData();
provide('toggle-appearance', async ({ clientX: x, clientY: y }) => {
if (!document.startViewTransition) {
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>
Key Points
- Import
vitepress/theme-without-fontsto use custom fonts - Use layout slots to inject content without overriding components
- Global components are registered in
enhanceApp - Override CSS variables for theming
- Use Vite aliases to replace internal components