Servicea11y

Window

Free-floating OS-style window opened programmatically via WrWindowManager. Drag the header to move, drag any edge or corner to resize, minimize to the taskbar. There is no declarative <wr-window> component — the manager is the only entry point so the API stays single-track. Need a backdrop + focus trap? That's WrDialog, not this.

Setup

Inject WrWindowManager to open windows. Drop <wr-window-taskbar /> once in your shell so minimized windows have somewhere to land.

import { WrWindowManager, WrWindowTaskbar } from 'ngwr/window';

@Component({
  imports: [WrWindowTaskbar],
  template: '<wr-window-taskbar />',
})
export class AppRoot {
  private readonly windows = inject(WrWindowManager);

  open(): void {
    this.windows.open(EditorComponent, { title: 'Editor' });
  }
}

Basic

manager.open(component, config) portals component as the window body. The returned WrWindowRef resolves afterClosed() with whatever the body passed to ref.close(value).

const ref = manager.open(EditorComponent, {
  title: 'Untitled.md',
  size: 'md',
  id: 'editor',                       // singleton: same id → same window
});

const result = await ref.afterClosed();
Open window

OS chrome presets

os swaps the action cluster style. 'auto' (the default) reads navigator.userAgentData.platform / navigator.platform and picks the right one for the visiting user — SSR-safe, unknown platforms fall back to windows.

manager.open(EditorComponent);                            // os: 'auto' (default)
manager.open(EditorComponent, { os: 'macos' });
manager.open(EditorComponent, { os: 'windows' });
manager.open(EditorComponent, { os: 'linux' });
Auto (detect)macOSWindowsLinux

Compact chrome

chromeSize: 'sm' shrinks the title bar to 1.625rem with smaller actions. Great for utility panels / docked tools.

Open compact window

Persisted position

storage: { key, prefix, persist } keeps geometry in WrStorage — drag the window, close, reopen, same geometry.

manager.open(EditorComponent, {
  id: 'editor',                              // stable id used by workspace save
  storage: {
    key: 'editor',                           // wr:window:my-app:editor
    prefix: 'my-app',
    persist: 'all',                          // 'position' | 'size' | 'all'
  },
});

// Drop the persisted state when you need a fresh open:
manager.clearPersistedPosition({ key: 'editor', prefix: 'my-app' });
Open persisted windowMove/resize, close it, click again — same spot.

Snap regions

snap: 'edges' snaps to halves; 'all' adds the four corners. A translucent rectangle previews the target while dragging.

manager.open(EditorComponent, {
  snap: 'all',  // drag to any edge to snap halves; corners give quarters
});
Open snappable window

Taskbar

<wr-window-taskbar /> lists every minimized window. Click a tab to restore + focus. Opt windows out via config.taskbar = false.

<wr-window-taskbar />          <!-- bottom (default) -->
<wr-window-taskbar position="top" />
Open inboxOpen editorOpen logsClick minimize on any of them — they land in the strip at the bottom of the page. These three pass showMinimize: true, because the Linux chrome is close-only by default and there would otherwise be nothing to press.

Workspace save / restore

saveLayout(name) snapshots every open window. restoreLayout(name) re-applies it once you've re-opened the matching components (matched by config.id).

// snapshot every open window's geometry + state
manager.saveLayout('default');

// later — for windows still open, geometry is re-applied in place. For
// windows that were closed, the opener callback re-creates them and the
// saved geometry is used as the seed instead of the cascade default.
manager.restoreLayout('default', (id, snap) => {
  if (id.startsWith('editor:')) {
    manager.open(EditorComponent, { id, title: snap.title });
  } else if (id === 'settings') {
    manager.open(SettingsComponent, { id, title: snap.title });
  }
});

// drop a snapshot
manager.clearLayout('default');
Save layoutRestore layoutClose all

Driving the window from inside

The projected component can inject(WR_WINDOW_REF) to get its own ref + inject(WR_WINDOW_DATA) to read the payload. Pair beforeClose with ref.close(value) for confirmation prompts.

// Inside the projected component
const ref = inject<WrWindowRef<MyComponent, MyResult>>(WR_WINDOW_REF);
const data = inject<MyData>(WR_WINDOW_DATA);

ref.beforeClose(async () => {
  if (!isDirty) return true;
  return await confirmDiscard();
});

ref.setTitle(`${docName} — ${isDirty ? 'unsaved' : 'saved'}`);

await save();
ref.close(savedDocId);

WrWindowConfig

NameDescriptionTypeDefault
WrWindowConfigOptions for WrWindowManager.open(component, config).interface
idStable identifier — used by the taskbar and workspace save. Auto-generated when omitted.stringauto
titleChrome title text.string''
osOS chrome preset. 'auto' reads the user's platform from the browser and picks the right one (SSR-safe).'auto' | 'macos' | 'windows' | 'linux''auto'
sizeInitial size preset. Overridden by width / height when provided.'sm' | 'md' | 'lg''md'
chromeSizeTitle-bar density. sm shrinks the bar + action dots for utility panels.'sm' | 'md''md'
x / y / width / heightExplicit initial geometry in px.numbercascade
minWidth / minHeight / maxWidth / maxHeightResize bounds.number220 / 140 / ∞ / ∞
movable / resizable / keepInViewportDrag, resize, viewport-clamp toggles.booleantrue
snapDrag-to-edge snap — edges (halves + maximise) or all (also corners).'none' | 'edges' | 'all''none'
showMinimize / showMaximizeRender the corresponding chrome action. Omit to follow the chrome — every OS but Linux shows both, and the Linux preset is close-only by convention. Pass true to override that, which is what a Linux user needs before a window can reach the taskbar.booleanper-OS
showCloseRender the close action. Not per-OS — every chrome closes.booleantrue
closeOnEscapeClose when the ESC key is pressed.booleantrue
taskbarShow in <wr-window-taskbar> when minimized.booleantrue
animationsOpen-fade + state transitions. Auto-disabled by prefers-reduced-motion.booleantrue
dragHandleCSS selector inside the projected content that restricts the move-grab area.string | nullnull
storage{ key, prefix?, persist? } — auto-save geometry to WrStorage, hydrate on next open.WrWindowStorageConfig
dataArbitrary payload — read it inside the projected component via inject(WR_WINDOW_DATA).Dnull

WrWindowManager

NameDescriptionTypeDefault
WrWindowManagerSingleton service — the only entry point for <wr-window>. Owns the stack, taskbar list, and workspace save.service
open(component, config?)Open a window with component as its body. Returns a WrWindowRef you can drive imperatively.<C, R>(c, cfg?) => WrWindowRef<C, R>
closeAll()Close every currently-open window.() => void
findById(id)Look up an open window by its config.idnull when no match.(id: string) => WrWindowRef | null
windowsSignal of every currently-open window.Signal<readonly WrWindowRef[]>
minimizedSignal of minimized windows opted into the taskbar — drives <wr-window-taskbar>.Signal<readonly WrWindowRef[]>
clearPersistedPosition(storage)Drop the saved geometry for a window so the next open uses config defaults.(cfg: WrWindowStorageConfig) => void
saveLayout(name)Persist every open window's geometry + state under name.(name: string) => void
restoreLayout(name, open?)Apply a saved workspace by id. Matching open windows get geometry re-applied in place. For missing windows, the optional open callback is invoked to re-create them — they seed at the saved coords (no cascade flicker).(name, open?: (id, snap) => void) => void
clearLayout(name)Drop a saved workspace.(name: string) => void

WrWindowRef

NameDescriptionTypeDefault
WrWindowRefHandle returned by manager.open(). Drive the window imperatively, await its result.class
idStable id (from config.id or auto-generated).string
componentInstanceThe projected component instance.C
state / x / y / width / height / z / titleLive geometry + state signals.Signal<…>
close(result?)Closes the window, running beforeClose if registered.(result?: R) => Promise<void>
afterClosed()Resolves with the close result.() => Promise<R | undefined>
beforeClose(hook)Register a guard — return falsy to veto a close.(hook) => void
minimize() / maximize() / restore() / focus()Lifecycle controls.() => void
moveTo(x, y) / resizeTo(w, h) / center()Programmatic geometry.() => void
setTitle(title)Update the chrome title.(title: string) => void

CSS variables

Custom properties ngwr/window publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.

VariableDefaultDeclared on
--wr-window-action-size1.5rem.wr-window +1 variant override
--wr-window-anim-duration0.18s.wr-window
--wr-window-bgvar(--wr-color-surface).wr-window
--wr-window-bordervar(--wr-color-outline).wr-window
--wr-window-chrome-bgvar(--wr-color-hover).wr-window
--wr-window-chrome-colorvar(--wr-color-on-surface).wr-window
--wr-window-chrome-height2.25rem.wr-window +1 variant override
--wr-window-handle-corner14px.wr-window
--wr-window-handle-size8px.wr-window
--wr-window-radiusvar(--wr-border-radius-base).wr-window +1 variant override
--wr-window-title-sizevar(--wr-text-sm).wr-window +1 variant override