All skills
antfu avatar

/vitepress

@d02c484 official
by Anthony Fuantfu/skills5.9k stars
335

VitePress static site generator powered by Vite and Vue. Use when building documentation sites, configuring themes, or writing Markdown with Vue components.

Use this Skill: https://skilld.dev/gh/antfu/skills/vitepress

This session only. Nothing lands on disk.

referencestheme-customization.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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.

Navbar (v2)

The navbar draws a single background surface controlled by CSS variables, so a frosted-glass bar needs no component overrides:

:root {
  --vp-nav-height: 4rem;
  --vp-nav-bg-color: color-mix(in srgb, var(--vp-c-bg) 65%, transparent);
  --vp-nav-home-bg-color: transparent; /* while unscrolled on the home page */
  --vp-nav-backdrop-filter: saturate(180%) blur(8px);
  --vp-nav-divider-color: var(--vp-c-gutter);
  --vp-nav-screen-bg-color: var(--vp-c-bg);
}

When nav items don't fit, they collapse into a ⋯ overflow menu (label it with extraMenuLabel). Note backdrop-filter has a scroll cost and Safari ≤17 skips variable-driven backdrop filters.

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-bottom
  • doc-before, doc-after
  • doc-footer-before
  • sidebar-nav-before, sidebar-nav-after
  • aside-top, aside-bottom
  • aside-outline-before, aside-outline-after
  • aside-ads-before, aside-ads-after

Home layout (layout: home):

  • home-hero-before, home-hero-after
  • home-hero-info-before, home-hero-info, home-hero-info-after
  • home-hero-actions-before-actions, home-hero-actions-after, home-hero-image
  • home-features-before, home-features-after

Page layout (layout: page):

  • page-top, page-bottom

Always available:

  • layout-top, layout-bottom
  • nav-bar-title-before, nav-bar-title-after
  • nav-bar-content-before, nav-bar-content-after
  • not-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>

Icons (v2)

Render iconify icons through VitePress's pipeline (no external fetch). Use the VPIcon component from vitepress/theme (accepts collection:name or { svg }):

<VPIcon icon="lucide:rocket" />

Or the useIcon composable, passing the element ref so dev mode can resolve it:

<script setup>
import { useIcon } from 'vitepress'
import { useTemplateRef } from 'vue'

const el = useTemplateRef('el')
const iconClass = useIcon('lucide:rocket', el)
</script>

<template><span ref="el" :class="iconClass" /></template>

Client-only icons (inside <ClientOnly>) aren't collected during build — list them in icons.include.

Route Change Hooks & setup (v2)

Assign navigation handlers on the router (return false to cancel), and use the setup hook to run Composition API code inside the root component's setup():

// .vitepress/theme/index.ts
import { watch } from 'vue'
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'

export default {
  extends: DefaultTheme,
  enhanceApp({ router }) {
    router.onBeforeRouteChange = (to) => { /* return false to cancel */ }
    router.onAfterRouteChange = (to) => console.log('navigated to', to)
  },
  setup() {
    const { page } = useData()
    watch(() => page.value.relativePath, (p) => console.log('now viewing', p))
  }
}

setup also runs during SSR/SSG — keep browser-only work in onMounted.

Key Points

  • Import vitepress/theme-without-fonts to use custom fonts
  • Use layout slots to inject content without overriding components
  • Global components are registered in enhanceApp
  • Override CSS variables for theming (v2 adds navbar surface variables)
  • Use Vite aliases to replace internal components
  • v2: VPIcon/useIcon for iconify icons, router onBeforeRouteChange/onAfterRouteChange, and a theme setup hook
<!-- Source references: - https://vitepress.dev/guide/extending-default-theme -->

Source: SKILL.md on GitHub

No alerts3d5 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and configuration examples for VitePress, a static site generator. No malicious patterns or intent were detected. A minor surface for indirect prompt injection was identified due to the documented features for ingesting external data during the site build process, which is inherent to the framework's functionality.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer7mo

    2/16 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d02c484. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 4 days ago
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/vuejs/vitepress, scripts located at https://github.com/antfu/skills"
}

README badge

README badge for antfu/skills/vitepress

VitePress is a static site generator built on Vite and Vue 3 that converts Markdown files into a fast single-page application, with file-based routing and built-in support for Vue components directly in Markdown. Use it for documentation sites, blogs, and marketing pages where you need configurable themes, syntax-highlighted code blocks, and instant hot-reload during development.

Generated from the current SKILL.md.

Does VitePress work with Vue components embedded in Markdown?
Yes. Vue components work directly in Markdown files, and you can use script setup and directives within Markdown content.
Can I build a multi-language documentation site with VitePress?
Yes. VitePress includes internationalization support with locale configuration for building multi-language sites.
What search options does VitePress provide?
VitePress includes built-in local search or integration with Algolia for full-text search across documentation.
Can I customize the default theme or build a custom one from scratch?
Yes. You can extend the default theme via CSS variables and slots, or build a completely custom theme by implementing the theme interface.
Does VitePress support dynamic route generation?
Yes. You can generate pages from data at build time using createContentLoader and paths loader files for dynamic routing.

Generated from the current SKILL.md. These answers refresh after source changes.