Every OWD theme registers a stable set of window chrome components. Apps rely on these names — not on theme-specific paths — so the same window markup works under Paper, Nova, Win95, and other shells.
DesktopWindowNavButton is the cross-theme primitive for any control in the title bar:
Each theme registers DesktopWindowNavButton.vue globally via addComponentsDir({ global: true }). Styling is theme-specific; the name and role are not.
| Requirement | Detail |
|---|---|
| File | runtime/components/DesktopWindowNavButton.vue (or equivalent global name) |
| Root element | Prefer <button type="button"> |
| Class | owd-window-nav__button on the root |
| Drag guard | @mousedown.stop on the root so clicks do not start window drag |
| Props | title?: string → title + aria-label |
| Slots | Default slot for icon or glyph |
| Legacy alias | Optional ButtonWindowNav.vue re-export for older apps |
Specialized controls (ButtonWindowNavClose, Win95 ButtonClose, Win11 kit Minimize/Close, …) compose DesktopWindowNavButton or match its sizing/hover in theme CSS.
Use DesktopWindowNavButton in #nav-prepend or #nav-append on DesktopWindow (or DesktopWindowIframe):
<DesktopWindow v-bind="$props">
<template #nav-append>
<DesktopWindowNavButton
v-if="showPlayer"
title="Back to home"
@click="closeVideo"
>
<Icon name="mdi:arrow-left" />
</DesktopWindowNavButton>
</template>
<!-- app content -->
</DesktopWindow>
Reference: app-youtube WindowYoutube.vue and app-soundcloud WindowSoundcloud.vue.
DesktopWindowNavButton is a stable contract — like DesktopWindow. The theme supplies pixels; the name stays fixed.Themes expose slots on DesktopWindow (and must forward them from DesktopWindowIframe):
| Slot | Position |
|---|---|
#nav-prepend | Left of the title |
#nav-append | Right of the title, before minimize / maximize / close |
#nav-title | Replace default title (Win11 only today) |
Forwarding chain:
App #nav-append
→ DesktopWindow #nav-append
→ DesktopWindowNav #append
→ .owd-window-nav__btn-group--append-inner
→ minimize / maximize / close
Inside DesktopWindowNav, the theme wraps the append slot in .owd-window-nav__btn-group--append-inner before window system controls:
<div class="owd-window-nav__btn-group owd-window-nav__btn-group--append">
<div
v-if="$slots.append"
class="owd-window-nav__btn-group owd-window-nav__btn-group--append-inner"
>
<slot name="append" />
</div>
<!-- minimize / maximize / close -->
</div>
Apps keep using #nav-append only. The inner wrapper is a theme layout concern — it groups app tools visually with caption controls (gap, divider, shared hover tokens).
Shared CSS classes (theme fills in colors):
.owd-window-nav__btn-group.owd-window-nav__btn-group--prepend.owd-window-nav__btn-group--append.owd-window-nav__btn-group--append-inner.owd-window-nav__buttonCore drag layer: DesktopCoreWindowNav (owd-window-nav__draggable).
If the theme ships DesktopWindowIframe, it must forward nav slots to DesktopWindow:
<DesktopWindow v-bind="$props">
<template #nav-prepend>
<slot name="nav-prepend" />
</template>
<template #nav-append>
<slot name="nav-append" />
</template>
<iframe :src="src" />
</DesktopWindow>
Without forwarding, iframe apps (e.g. YouTube Music play control) lose title-bar actions.
DesktopWindowNavButton globally.DesktopWindowNav with prepend / title / append-inner / system controls.nav-prepend and nav-append from DesktopWindow and DesktopWindowIframe..owd-window-nav__button and .owd-window-nav__btn-group--append-inner in theme SCSS.ButtonWindowNav as a deprecated alias.