Skip to content

Themes

Explore VitePress themes, learn how to customize the default theme, and create your own custom themes.

Default Theme

VitePress comes with a beautiful, responsive default theme that's perfect for documentation sites, blogs, and marketing pages.

Features

  • 🎨 Modern Design: Clean, professional appearance
  • 📱 Responsive: Works perfectly on all devices
  • 🌙 Dark Mode: Built-in dark/light mode toggle
  • 🔍 Search: Integrated local search functionality
  • 🧭 Navigation: Flexible nav and sidebar configuration
  • Performance: Optimized for speed and SEO

Theme Configuration

js
// .vitepress/config.js
export default {
  themeConfig: {
    // Logo
    logo: '/logo.svg',
    siteTitle: 'My Documentation',
    
    // Navigation
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Guide', link: '/guide/' },
      {
        text: 'Dropdown',
        items: [
          { text: 'Item A', link: '/item-a' },
          { text: 'Item B', link: '/item-b' }
        ]
      }
    ],
    
    // Sidebar
    sidebar: {
      '/guide/': [
        {
          text: 'Getting Started',
          collapsed: false,
          items: [
            { text: 'Introduction', link: '/guide/' },
            { text: 'Installation', link: '/guide/installation' }
          ]
        }
      ]
    },
    
    // Social links
    socialLinks: [
      { icon: 'github', link: 'https://github.com/username/repo' },
      { icon: 'twitter', link: 'https://twitter.com/username' },
      { icon: 'discord', link: 'https://discord.gg/invite' }
    ],
    
    // Footer
    footer: {
      message: 'Released under the MIT License.',
      copyright: 'Copyright © 2024 Your Name'
    },
    
    // Edit link
    editLink: {
      pattern: 'https://github.com/username/repo/edit/main/docs/:path',
      text: 'Edit this page on GitHub'
    },
    
    // Last updated
    lastUpdated: {
      text: 'Updated at',
      formatOptions: {
        dateStyle: 'full',
        timeStyle: 'medium'
      }
    }
  }
}

Theme Customization

CSS Variables

Customize the default theme using CSS custom properties:

css
/* .vitepress/theme/custom.css */
:root {
  /* Colors */
  --vp-c-brand: #646cff;
  --vp-c-brand-light: #747bff;
  --vp-c-brand-dark: #535bf2;
  --vp-c-brand-darker: #454ce1;
  
  /* Typography */
  --vp-font-family-base: 'Inter', sans-serif;
  --vp-font-family-mono: 'Fira Code', monospace;
  
  /* Layout */
  --vp-layout-max-width: 1440px;
  --vp-sidebar-width: 272px;
  
  /* Shadows */
  --vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
  --vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07);
}

/* Dark mode overrides */
.dark {
  --vp-c-bg: #1a1a1a;
  --vp-c-bg-alt: #262626;
  --vp-c-bg-elv: #2c2c2c;
}

Custom Components

Add custom components to enhance your theme:

vue
<!-- .vitepress/theme/components/CustomHero.vue -->
<template>
  <div class="custom-hero">
    <div class="hero-content">
      <h1 class="hero-title">{{ title }}</h1>
      <p class="hero-description">{{ description }}</p>
      <div class="hero-actions">
        <a 
          v-for="action in actions" 
          :key="action.text"
          :href="action.link"
          :class="['hero-action', `hero-action-${action.theme}`]"
        >
          {{ action.text }}
        </a>
      </div>
    </div>
    <div class="hero-image">
      <img :src="image" :alt="imageAlt" />
    </div>
  </div>
</template>

<script setup>
defineProps({
  title: String,
  description: String,
  image: String,
  imageAlt: String,
  actions: Array
})
</script>

<style scoped>
.custom-hero {
  display: flex;
  align-items: center;
  min-height: 60vh;
  padding: 2rem 0;
}

.hero-content {
  flex: 1;
  padding-right: 2rem;
}

.hero-title {
  font-size: 3rem;
  font-weight: 800;
  line-height: 1.1;
  margin-bottom: 1rem;
  background: linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-light));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero-description {
  font-size: 1.25rem;
  color: var(--vp-c-text-2);
  margin-bottom: 2rem;
  line-height: 1.6;
}

.hero-actions {
  display: flex;
  gap: 1rem;
}

.hero-action {
  padding: 0.75rem 1.5rem;
  border-radius: 6px;
  text-decoration: none;
  font-weight: 600;
  transition: all 0.2s ease;
}

.hero-action-brand {
  background: var(--vp-c-brand);
  color: white;
}

.hero-action-brand:hover {
  background: var(--vp-c-brand-dark);
  transform: translateY(-1px);
}

.hero-action-alt {
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-1);
  border: 1px solid var(--vp-c-divider);
}

.hero-action-alt:hover {
  border-color: var(--vp-c-brand);
}

.hero-image {
  flex: 1;
  text-align: center;
}

.hero-image img {
  max-width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: var(--vp-shadow-2);
}

@media (max-width: 768px) {
  .custom-hero {
    flex-direction: column;
    text-align: center;
  }
  
  .hero-content {
    padding-right: 0;
    margin-bottom: 2rem;
  }
  
  .hero-title {
    font-size: 2rem;
  }
}
</style>

Custom Theme Development

Creating a Custom Theme

Create a completely custom theme from scratch:

js
// .vitepress/theme/index.js
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'
import './styles/main.css'

export default {
  Layout,
  NotFound,
  enhanceApp({ app, router, siteData }) {
    // Register global components
    app.component('CustomButton', CustomButton)
    
    // Add global properties
    app.config.globalProperties.$customMethod = () => {
      console.log('Custom method called')
    }
    
    // Router hooks
    router.onBeforeRouteChange = (to) => {
      console.log('Navigating to:', to)
    }
  }
}

Theme Structure

.vitepress/theme/
├── index.js              # Theme entry point
├── Layout.vue            # Main layout component
├── NotFound.vue          # 404 page component
├── components/           # Reusable components
│   ├── Header.vue
│   ├── Sidebar.vue
│   ├── Footer.vue
│   └── CustomButton.vue
├── styles/               # Theme styles
│   ├── main.css
│   ├── variables.css
│   └── components.css
└── utils/                # Utility functions
    ├── theme.js
    └── helpers.js

Main Layout Component

vue
<!-- .vitepress/theme/Layout.vue -->
<template>
  <div class="theme-layout">
    <Header />
    
    <div class="layout-container">
      <Sidebar v-if="showSidebar" />
      
      <main class="main-content">
        <div class="content-wrapper">
          <Content />
        </div>
      </main>
    </div>
    
    <Footer />
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useData } from 'vitepress'
import Header from './components/Header.vue'
import Sidebar from './components/Sidebar.vue'
import Footer from './components/Footer.vue'

const { page, frontmatter } = useData()

const showSidebar = computed(() => {
  return frontmatter.value.sidebar !== false && 
         page.value.relativePath !== 'index.md'
})
</script>

<style scoped>
.theme-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.layout-container {
  flex: 1;
  display: flex;
}

.main-content {
  flex: 1;
  padding: 2rem;
}

.content-wrapper {
  max-width: 800px;
  margin: 0 auto;
}

@media (max-width: 768px) {
  .layout-container {
    flex-direction: column;
  }
  
  .main-content {
    padding: 1rem;
  }
}
</style>

Theme Examples

Blog Theme

vue
<!-- Blog-specific layout -->
<template>
  <div class="blog-layout">
    <header class="blog-header">
      <h1>{{ siteData.title }}</h1>
      <nav>
        <a href="/">Home</a>
        <a href="/posts/">Posts</a>
        <a href="/about/">About</a>
      </nav>
    </header>
    
    <main class="blog-main">
      <article v-if="frontmatter.layout === 'post'" class="blog-post">
        <header class="post-header">
          <h1>{{ frontmatter.title }}</h1>
          <div class="post-meta">
            <time>{{ formatDate(frontmatter.date) }}</time>
            <span class="tags">
              <span 
                v-for="tag in frontmatter.tags" 
                :key="tag"
                class="tag"
              >
                {{ tag }}
              </span>
            </span>
          </div>
        </header>
        <Content />
      </article>
      
      <div v-else>
        <Content />
      </div>
    </main>
    
    <footer class="blog-footer">
      <p>&copy; 2024 My Blog. All rights reserved.</p>
    </footer>
  </div>
</template>

<script setup>
import { useData } from 'vitepress'

const { siteData, frontmatter } = useData()

const formatDate = (date) => {
  return new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  })
}
</script>

<style scoped>
.blog-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: 'Georgia', serif;
}

.blog-header {
  background: var(--vp-c-bg-soft);
  padding: 2rem;
  text-align: center;
  border-bottom: 1px solid var(--vp-c-divider);
}

.blog-header h1 {
  margin: 0 0 1rem 0;
  font-size: 2.5rem;
  color: var(--vp-c-text-1);
}

.blog-header nav {
  display: flex;
  justify-content: center;
  gap: 2rem;
}

.blog-header nav a {
  color: var(--vp-c-text-2);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.2s ease;
}

.blog-header nav a:hover {
  color: var(--vp-c-brand);
}

.blog-main {
  flex: 1;
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

.post-header {
  margin-bottom: 2rem;
  text-align: center;
}

.post-header h1 {
  font-size: 2.5rem;
  margin-bottom: 1rem;
  color: var(--vp-c-text-1);
}

.post-meta {
  color: var(--vp-c-text-2);
  font-size: 0.9rem;
}

.tags {
  margin-left: 1rem;
}

.tag {
  background: var(--vp-c-brand);
  color: white;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.8rem;
  margin-right: 0.5rem;
}

.blog-footer {
  background: var(--vp-c-bg-soft);
  padding: 2rem;
  text-align: center;
  border-top: 1px solid var(--vp-c-divider);
}
</style>

Portfolio Theme

vue
<!-- Portfolio-specific layout -->
<template>
  <div class="portfolio-layout">
    <nav class="portfolio-nav">
      <div class="nav-brand">
        <h2>{{ siteData.title }}</h2>
      </div>
      <div class="nav-links">
        <a href="/">Home</a>
        <a href="/projects/">Projects</a>
        <a href="/about/">About</a>
        <a href="/contact/">Contact</a>
      </div>
    </nav>
    
    <main class="portfolio-main">
      <Content />
    </main>
  </div>
</template>

<script setup>
import { useData } from 'vitepress'

const { siteData } = useData()
</script>

<style scoped>
.portfolio-layout {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

.portfolio-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
}

.nav-brand h2 {
  margin: 0;
  font-size: 1.5rem;
}

.nav-links {
  display: flex;
  gap: 2rem;
}

.nav-links a {
  color: white;
  text-decoration: none;
  font-weight: 500;
  transition: opacity 0.2s ease;
}

.nav-links a:hover {
  opacity: 0.8;
}

.portfolio-main {
  padding: 4rem 2rem;
  max-width: 1200px;
  margin: 0 auto;
}
</style>

Theme Plugins

Adding Third-party Integrations

js
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import { createPinia } from 'pinia'
import VueGtag from 'vue-gtag'

export default {
  extends: DefaultTheme,
  enhanceApp({ app, router }) {
    // Pinia state management
    const pinia = createPinia()
    app.use(pinia)
    
    // Google Analytics
    app.use(VueGtag, {
      config: {
        id: 'GA_MEASUREMENT_ID'
      }
    }, router)
    
    // Global error handler
    app.config.errorHandler = (err, vm, info) => {
      console.error('Theme error:', err, info)
    }
  }
}

Custom Directives

js
// .vitepress/theme/directives/tooltip.js
export default {
  mounted(el, binding) {
    el.setAttribute('title', binding.value)
    el.style.cursor = 'help'
    el.style.borderBottom = '1px dotted currentColor'
  },
  updated(el, binding) {
    el.setAttribute('title', binding.value)
  }
}

// Register in theme
import tooltipDirective from './directives/tooltip.js'

export default {
  enhanceApp({ app }) {
    app.directive('tooltip', tooltipDirective)
  }
}

Community Themes

VitePress Theme Hope

Feature-rich theme with blog support and extensive customization options.

bash
npm install vitepress-theme-hope

VitePress Theme Demoblock

Theme with live code demonstration blocks.

bash
npm install vitepress-theme-demoblock

VitePress Theme Sidebar

Enhanced sidebar with advanced navigation features.

bash
npm install @escook/vitepress-theme-sidebar

Theme Marketplace

Theme Development Best Practices

Performance Optimization

css
/* Use CSS custom properties for theming */
:root {
  --primary-color: #3b82f6;
  --secondary-color: #64748b;
}

/* Optimize animations */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s ease;
}

/* Use efficient selectors */
.component-class {
  /* Avoid deep nesting */
}

Accessibility

vue
<template>
  <!-- Use semantic HTML -->
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/" aria-current="page">Home</a></li>
      <li><a href="/about/">About</a></li>
    </ul>
  </nav>
  
  <!-- Provide alt text for images -->
  <img src="/hero.jpg" alt="Hero image showing the product interface" />
  
  <!-- Use proper heading hierarchy -->
  <h1>Main Title</h1>
  <h2>Section Title</h2>
  <h3>Subsection Title</h3>
</template>

Mobile Responsiveness

css
/* Mobile-first approach */
.component {
  padding: 1rem;
}

/* Tablet */
@media (min-width: 768px) {
  .component {
    padding: 2rem;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .component {
    padding: 3rem;
  }
}

/* Use flexible layouts */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
}

Testing Themes

Visual Testing

bash
# Install visual testing tools
npm install -D @storybook/vue3
npm install -D chromatic

# Create component stories
# stories/Button.stories.js
export default {
  title: 'Components/Button',
  component: Button
}

export const Primary = {
  args: {
    variant: 'primary',
    text: 'Click me'
  }
}

Cross-browser Testing

js
// Use modern CSS with fallbacks
.component {
  background: #3b82f6; /* Fallback */
  background: var(--primary-color, #3b82f6);
  
  /* Use feature queries */
  @supports (backdrop-filter: blur(10px)) {
    backdrop-filter: blur(10px);
  }
}

Theme Distribution

Publishing to npm

json
{
  "name": "vitepress-theme-custom",
  "version": "1.0.0",
  "description": "A custom VitePress theme",
  "main": "index.js",
  "files": [
    "components",
    "styles",
    "index.js"
  ],
  "keywords": [
    "vitepress",
    "theme",
    "vue"
  ],
  "peerDependencies": {
    "vitepress": "^1.0.0",
    "vue": "^3.0.0"
  }
}

Documentation

markdown
# My VitePress Theme

A beautiful and customizable theme for VitePress.

## Installation

```bash
npm install vitepress-theme-custom

Usage

js
// .vitepress/theme/index.js
import Theme from 'vitepress-theme-custom'

export default Theme

Configuration

js
// .vitepress/config.js
export default {
  themeConfig: {
    customOption: 'value'
  }
}

## Resources

### Official Documentation
- [VitePress Theming Guide](https://vitepress.dev/guide/custom-theme)
- [Vue.js Documentation](https://vuejs.org/)
- [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*)

### Design Resources
- [Design Systems](https://designsystemsrepo.com/)
- [Color Palettes](https://coolors.co/)
- [Typography](https://fonts.google.com/)
- [Icons](https://lucide.dev/)

### Community
- [VitePress Discord](https://discord.gg/vue)
- [GitHub Discussions](https://github.com/vuejs/vitepress/discussions)
- [Vue.js Community](https://vuejs.org/community/)

---

*Theme development is an ongoing process. Keep experimenting and contributing to the VitePress ecosystem!*

VitePress Development Guide