Themes

Theme anatomy

module.ts, desktop runtimeConfig, Desktop.vue, defu merge, and kit dependencies.

Entry: module.ts

Typical pattern (Nova / Win95 / Paper):

import { createResolver, addComponentsDir, addPlugin, installModule } from '@nuxt/kit'
import { defineDesktopTheme } from '@owdproject/core'
import { registerThemeTailwindPath } from '@owdproject/kit-tailwind/kit/registerTailwindPath'

export default defineDesktopTheme({
  meta: { name: 'desktop-theme-nova' },
  defaults: {
    name: 'nova',
    systemBar: { enabled: true, position: 'top', startButton: true },
    dockBar: { enabled: true, position: 'bottom' },
  },
  async setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    await installModule('@owdproject/kit-primevue')
    registerThemeTailwindPath(nuxt, import.meta.url)

    addComponentsDir({ path: resolve('./runtime/components'), global: true })

    addPlugin({
      src: resolve('./runtime/plugins/50.desktop-theme-nova-dialogs.client.ts'),
      mode: 'client',
    })
  },
})

Avoid anti-patterns removed in recent themes:

  • Duplicating a paperShell object that repeats defaults.
  • deepMerge(deepMerge(a, b), c) — use defu and module defaults instead.
  • Forcing nested keys after merge (e.g. overwriting systemBar) — put them in defaults.

Merging runtimeConfig.public.desktop

Core has already applied desktop.config.ts. The theme fills undefined keys only:

defu(existingDesktop, options)

User config wins on conflicts — correct for per-project overrides in desktop.config.ts.

Some themes (GNOME) also import a local desktop.config.ts inside the theme repo for packaged defaults:

const themeDefaults = (await import(resolve('./desktop.config.ts'))).default
nuxt.options.runtimeConfig.public.desktop = defu(
  nuxt.options.runtimeConfig.public.desktop ?? {},
  defu(themeDefaults, options),
)

Desktop.vue

runtime/components/Desktop.vue is the theme entry component. The consumer desktop project mounts it from app.vue:

<template>
  <Desktop />
</template>

Responsibilities

LayerOwnerRole
DesktopCore@owdproject/coreWindow manager, workspaces, app mounting, z-order
Desktop.vueThemeShell layout — background, system bar, dock, slots
Window chromeTheme (Window*.vue)Title bar, resize handles, theme styling
App contentApp modulesBusiness UI inside window content slot

Typical structure

<template>
  <DesktopCore>
    <template #background>
      <Background />
    </template>
    <template #system-bar>
      <SystemBar />
    </template>
    <template #dock>
      <DockBar v-if="desktop.dockBar?.enabled" />
    </template>
  </DesktopCore>
</template>

Exact slot names follow DesktopCore API — inspect core’s DesktopCore.vue or reference theme-nova Desktop.

Paper uses a thinner wrapper; Nova adds dock, explorer integration, and richer chrome. Start minimal, add slots as the shell grows.

Global registration

addComponentsDir({ global: true }) in module.ts auto-imports Desktop, SystemBar, etc. The component name matches the filename (Desktop.vue<Desktop />).

Other components

FileRole
Window*.vueWrap DesktopWindow / DesktopWindowContent.
DesktopWindowNavButton.vueTitle-bar button primitive (close, min/max, app tools). See Window chrome and nav buttons.
SystemBar, Background, …OS chrome

Kits and optional modules

DependencyWhen
@owdproject/kit-primevueAlmost always for standard themes — handles PrimeVue dialogs, Tailwind CSS, and explorer UI components.
@owdproject/module-fsRequired when adding filesystem support — provides headless virtual filesystem logic and explorer stores.

See Theme and optional modules.

Peer dependency

Declare @owdproject/core": "^3.4.0" and @owdproject/kit-primevue": "^3.4.0" as peers so install warns on API skew.

Themes that import PrimeVue directly should also peer primevue and @primeuix/themes.

In the client monorepo, devDependencies may use workspace:* for core while developing kits and themes together — see Package linking.

Tailwind

Call registerThemeTailwindPath once in theme setup, and call registerTailwindPath (both imported from @owdproject/kit-tailwind/kit/registerTailwindPath) for any custom paths containing Tailwind classes.