Components You Can Import, Not Just Configure

L. Ipsum

November 16, 2025

Categorized as VitePress and Static Sites

Most themes expose customization through config objects: flip a boolean, set a string, and hope the option you need exists. LTheme does that too — avatar, nav, socialLinks, footer are all plain configSee the ThemeConfig type in src/theme/types.ts for the full shape.. But some things aren't really config-shaped. A badge that renders differently depending on what you want it to say isn't a boolean — it's a component. So the theme's package entry exports a handful of them directly.

The export

src/index.ts, the package's ltheme/theme entry, re-exports the default theme alongside named components:

ts
import Theme from './theme'

export { OpenToWorkBadge } from './shared'

export default Theme

OpenToWorkBadge itself is nothing exotic — a small .vue file under src/shared/components/:

vue
<template>
  <div v-if="openToWork?.text" class="open-to-work">
    <a v-if="openToWork.link" class="status-badge" :href="withBase(openToWork.link)">
      {{ openToWork.text }}
    </a>
  </div>
</template>

Using it

OpenToWorkBadge isn't wired into the theme by default — it's opt-in. This site's own docs/.vitepress/theme/index.ts currently ships without it, just export default Theme. Turning it on means wrapping the Layout to fill the slot:

ts
import { h } from 'vue'
import Theme, { OpenToWorkBadge } from 'ltheme/theme'

export default {
  ...Theme,
  Layout: () =>
    h(Theme.Layout, null, {
      'sidebar-badge': () => h(OpenToWorkBadge)
    })
}

Layout.vue exposes a sidebar-badge slot precisely so a consumer can swap in their own badge, or none at all, without forking the layout:

vue
<template #sidebar-badge>
  <slot name="sidebar-badge" />
</template>

Why export a component instead of a config flag

A config flag like showOpenToWorkBadge: true only gets you the one badge the theme author imagined. Exporting the component — and a named slot to place it in — gets you any badge: swap OpenToWorkBadge for your own <AvailableForContractBadge />, or nothing at all if you don't want one. The theme controls the slot, you control what fills it. That's the same reasoning behind exposing the raw markdown-it instance for the sidenote and :::writing pluginscovered in Why VitePress — extension points instead of a longer and longer list of config options.