Skip to content

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

VitePress Development Guide