---
name: extending-vitepress-default-theme
description: Customize CSS variables, use layout slots, register global components, and override theme fonts
---

# 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:

```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import './custom.css'

export default DefaultTheme
```

## CSS Variables

Override root CSS variables:

```css
/* .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](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css).

## Home Hero Customization

```css
: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:

```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme-without-fonts'
import './fonts.css'

export default DefaultTheme
```

```css
/* .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:

```ts
// .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:

```ts
// .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:

```md
<MyComponent :prop="value" />
```

## Layout Slots

Inject content into specific locations:

```ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'

export default {
  extends: DefaultTheme,
  Layout: MyLayout
}
```

```vue
<!-- .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-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:

```ts
// .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:

```ts
// .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:

```vue
<!-- .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>
```

## 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
- Use Vite aliases to replace internal components

<!--
Source references:
- https://vitepress.dev/guide/extending-default-theme
-->
