Extending Default Theme
Learn how to extend and customize the VitePress default theme.
Theme Extension Basics
Extending the Default Theme
Create a custom theme that extends the default:
javascript
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import './custom.css'
export default {
extends: DefaultTheme,
enhanceApp({ app, router, siteData }) {
// App-level enhancements
}
}
Theme Structure
.vitepress/
└── theme/
├── index.js # Theme entry point
├── custom.css # Custom styles
├── components/ # Custom components
└── layouts/ # Custom layouts
Layout Customization
Using Layout Slots
The default theme provides many slots for customization:
vue
<!-- .vitepress/theme/Layout.vue -->
<template>
<Layout>
<!-- Navigation slots -->
<template #nav-bar-title-before>
<div class="custom-logo">
<img src="/logo.svg" alt="Logo" />
</div>
</template>
<template #nav-bar-content-after>
<div class="custom-nav-item">
<a href="/sponsor">Sponsor</a>
</div>
</template>
<!-- Sidebar slots -->
<template #sidebar-nav-before>
<div class="sidebar-custom-block">
<h3>Quick Links</h3>
<ul>
<li><a href="/quick-start">Quick Start</a></li>
<li><a href="/examples">Examples</a></li>
</ul>
</div>
</template>
<!-- Content slots -->
<template #doc-before>
<div class="doc-notice">
<p>📝 This documentation is constantly updated</p>
</div>
</template>
<template #doc-after>
<div class="doc-footer">
<p>Was this page helpful?</p>
<button @click="helpful(true)">👍 Yes</button>
<button @click="helpful(false)">👎 No</button>
</div>
</template>
<!-- Home page slots -->
<template #home-hero-after>
<div class="custom-hero-section">
<h2>Why Choose Our Solution?</h2>
<div class="features-grid">
<div class="feature">
<h3>🚀 Fast</h3>
<p>Lightning fast performance</p>
</div>
<div class="feature">
<h3>🔧 Flexible</h3>
<p>Highly customizable</p>
</div>
</div>
</div>
</template>
</Layout>
</template>
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
function helpful(isHelpful) {
// Track feedback
console.log('Page helpful:', isHelpful)
}
</script>
Available Layout Slots
javascript
// Navigation slots
'nav-bar-title-before'
'nav-bar-title-after'
'nav-bar-content-before'
'nav-bar-content-after'
'nav-screen-content-before'
'nav-screen-content-after'
// Sidebar slots
'sidebar-nav-before'
'sidebar-nav-after'
// Content slots
'page-top'
'page-bottom'
'doc-before'
'doc-after'
'doc-footer-before'
// Home page slots
'home-hero-before'
'home-hero-info'
'home-hero-actions'
'home-hero-after'
'home-features-before'
'home-features-after'
// Aside slots
'aside-top'
'aside-bottom'
'aside-outline-before'
'aside-outline-after'
'aside-ads-before'
'aside-ads-after'
// Special slots
'not-found'
'layout-top'
'layout-bottom'
Component Overrides
Overriding Default Components
Replace default theme components:
javascript
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import CustomNavBar from './components/CustomNavBar.vue'
import CustomSidebar from './components/CustomSidebar.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
// Override default components
app.component('VPNavBar', CustomNavBar)
app.component('VPSidebar', CustomSidebar)
}
}
Custom Navigation Bar
vue
<!-- .vitepress/theme/components/CustomNavBar.vue -->
<template>
<header class="custom-navbar">
<div class="navbar-container">
<div class="navbar-brand">
<a href="/" class="brand-link">
<img src="/logo.svg" alt="Logo" class="brand-logo" />
<span class="brand-title">{{ site.title }}</span>
</a>
</div>
<nav class="navbar-nav">
<div class="nav-links">
<a
v-for="item in theme.nav"
:key="item.text"
:href="item.link"
class="nav-link"
>
{{ item.text }}
</a>
</div>
<div class="nav-actions">
<button @click="toggleDark" class="theme-toggle">
{{ isDark ? '☀️' : '🌙' }}
</button>
<a href="https://github.com/your-repo" class="github-link">
<GitHubIcon />
</a>
</div>
</nav>
</div>
</header>
</template>
<script setup>
import { useData } from 'vitepress'
import { useDark, useToggle } from '@vueuse/core'
import GitHubIcon from './GitHubIcon.vue'
const { site, theme } = useData()
const isDark = useDark()
const toggleDark = useToggle(isDark)
</script>
<style scoped>
.custom-navbar {
background: var(--vp-nav-bg-color);
border-bottom: 1px solid var(--vp-c-divider);
}
.navbar-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
height: 64px;
}
.brand-link {
display: flex;
align-items: center;
text-decoration: none;
}
.brand-logo {
width: 32px;
height: 32px;
margin-right: 12px;
}
.navbar-nav {
display: flex;
align-items: center;
gap: 24px;
}
.nav-links {
display: flex;
gap: 24px;
}
.nav-link {
color: var(--vp-c-text-1);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.nav-link:hover {
color: var(--vp-c-brand-1);
}
.nav-actions {
display: flex;
align-items: center;
gap: 12px;
}
.theme-toggle {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
}
.theme-toggle:hover {
background: var(--vp-c-gray-light-4);
}
</style>
Style Customization
CSS Variable Overrides
Customize theme colors and spacing:
css
/* .vitepress/theme/custom.css */
:root {
/* Brand colors */
--vp-c-brand-1: #3eaf7c;
--vp-c-brand-2: #369870;
--vp-c-brand-3: #2d8063;
--vp-c-brand-soft: rgba(62, 175, 124, 0.14);
/* Background colors */
--vp-c-bg: #ffffff;
--vp-c-bg-alt: #f6f6f7;
--vp-c-bg-elv: #ffffff;
--vp-c-bg-soft: #f6f6f7;
/* Text colors */
--vp-c-text-1: rgba(60, 60, 67);
--vp-c-text-2: rgba(60, 60, 67, 0.78);
--vp-c-text-3: rgba(60, 60, 67, 0.56);
/* Layout */
--vp-layout-max-width: 1440px;
--vp-nav-height: 64px;
--vp-sidebar-width: 272px;
/* Typography */
--vp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
--vp-font-family-mono: 'Fira Code', Menlo, Monaco, Consolas, 'Courier New', monospace;
}
/* Dark theme overrides */
.dark {
--vp-c-bg: #1b1b1f;
--vp-c-bg-alt: #161618;
--vp-c-bg-elv: #202127;
--vp-c-bg-soft: #202127;
--vp-c-text-1: rgba(255, 255, 245, 0.86);
--vp-c-text-2: rgba(235, 235, 245, 0.6);
--vp-c-text-3: rgba(235, 235, 245, 0.38);
}
Component-Specific Styles
Target specific components:
css
/* Navigation customization */
.VPNavBar {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.85);
}
.dark .VPNavBar {
background: rgba(27, 27, 31, 0.85);
}
/* Sidebar customization */
.VPSidebar {
border-right: 1px solid var(--vp-c-divider);
}
.VPSidebarItem.level-0 > .item > .link {
font-weight: 600;
}
/* Content customization */
.VPContent {
padding-top: 32px;
}
.VPDoc .container {
max-width: 864px;
}
/* Home page customization */
.VPHome .name {
background: linear-gradient(120deg, #bd34fe 30%, #41d1ff);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
Adding Custom Features
Search Integration
Add custom search functionality:
javascript
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import CustomSearch from './components/CustomSearch.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('CustomSearch', CustomSearch)
}
}
Analytics Integration
Add analytics tracking:
javascript
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
enhanceApp({ app, router }) {
// Google Analytics
if (typeof window !== 'undefined') {
// Load GA script
const script = document.createElement('script')
script.src = 'https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID'
document.head.appendChild(script)
window.dataLayer = window.dataLayer || []
function gtag() { dataLayer.push(arguments) }
gtag('js', new Date())
gtag('config', 'GA_MEASUREMENT_ID')
// Track page views
router.onAfterRouteChanged = (to) => {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: to
})
}
}
}
}
Best Practices
Performance
- Minimize custom CSS
- Use CSS variables for theming
- Optimize custom components
- Lazy load heavy features
Maintainability
- Keep customizations minimal
- Document custom changes
- Test across different screen sizes
- Follow VitePress conventions
Accessibility
- Maintain keyboard navigation
- Ensure proper contrast ratios
- Use semantic HTML
- Test with screen readers