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>© 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
Popular Third-party 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!*