Files
stack/v1/packages/mosaic/framework/skills/vitepress/references/theme-customization.md
T

6.1 KiB

name, description
name description
extending-vitepress-default-theme 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:

// .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.

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

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