Themes

Window chrome and nav buttons

DesktopWindowNavButton, nav-append-inner, and title-bar slots every theme must implement.

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

DesktopWindowNavButton is the cross-theme primitive for any control in the title bar:

  • Close, minimize, maximize (wrapped by the theme)
  • Custom app actions (back, play, load ROM, …)

Each theme registers DesktopWindowNavButton.vue globally via addComponentsDir({ global: true }). Styling is theme-specific; the name and role are not.

Theme obligation

RequirementDetail
Fileruntime/components/DesktopWindowNavButton.vue (or equivalent global name)
Root elementPrefer <button type="button">
Classowd-window-nav__button on the root
Drag guard@mousedown.stop on the root so clicks do not start window drag
Propstitle?: stringtitle + aria-label
SlotsDefault slot for icon or glyph
Legacy aliasOptional 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.

App usage

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.

Intentional exception: apps normally avoid theme-only components, but DesktopWindowNavButton is a stable contract — like DesktopWindow. The theme supplies pixels; the name stays fixed.

Title bar slots

Themes expose slots on DesktopWindow (and must forward them from DesktopWindowIframe):

SlotPosition
#nav-prependLeft of the title
#nav-appendRight of the title, before minimize / maximize / close
#nav-titleReplace 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

append-inner

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__button

Core drag layer: DesktopCoreWindowNav (owd-window-nav__draggable).

DesktopWindowIframe

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.

Checklist for new themes

  1. Register DesktopWindowNavButton globally.
  2. Implement DesktopWindowNav with prepend / title / append-inner / system controls.
  3. Forward nav-prepend and nav-append from DesktopWindow and DesktopWindowIframe.
  4. Style .owd-window-nav__button and .owd-window-nav__btn-group--append-inner in theme SCSS.
  5. Optionally keep ButtonWindowNav as a deprecated alias.