Skill v1.0.0
currentTrusted Publisher100/100version: "1.0.0" name: reactor-windowing description: "Reactor top-level windowing cookbook: WindowSpec, OpenWindow, draggable windows, borderless/tool windows, placement persistence, taskbar visibility, z-order, aspect ratio, SizeToContent, displays, taskbar integration, and picker HWND wiring." when_to_invoke:
- "open a window"
- "secondary window"
- "make a window draggable"
- "remember window position"
- "restore window placement"
- "tool palette"
- "command palette"
- "window aspect ratio"
- "borderless window"
- "always on top"
- "taskbar progress"
- "file picker"
Reactor Windowing
Use this skill when a task involves top-level windows, placement, shell chrome, taskbar features, displays, or native pickers.
Core APIs
var win = ReactorApp.OpenWindow(new WindowSpec { Title = "Settings", Width = 520, Height = 420 },() => new SettingsWindow());win.Activate();win.Close();
WindowSpec is immutable startup intent. ReactorWindow is the live handle for runtime mutators (SetSize, SetPosition, SetAspectRatio, BeginDragMove, SavePlacement, Update).
Size and resize
new WindowSpec{ResizeMode = WindowResizeMode.CanMinimize, // CanResize | NoResize | CanMinimizeAspectRatio = 16.0 / 9.0,MinWidth = 480,SizeToContent = WindowSizeToContent.Manual, // Manual | Width | Height | WidthAndHeight};UseWindow()?.SetAspectRatio(4.0 / 3.0);UseWindowAspectRatio(1.0); // scoped; unmount clears
Rules:
AspectRatiocannot combine withResizeMode.NoResize.AspectRatiocannot combine withSizeToContent.SizeToContentignores maximized windows and may settle one frame after mount.
Movement, drag, and persistence
new WindowSpec{StartPosition = WindowStartPosition.CenterOnCurrent,IsMovableByBackground = true,};var (x, y) = UseWindowPosition();var drag = UseWindowDragMove();Button("Drag", drag);Border(customEditor).Drag(false); // opt out of background drag
var spec = new WindowSpec { Title = "Shell" }.WithPersistence("shell-main", WindowStartPosition.CenterOnCurrent);UseWindow()?.SavePlacement();
Persistence requires PersistPlacement; use .WithPersistence(...) for the common case. PersistenceId alone is only identity for persistence systems.
Placement goes to an auto-picked store: ApplicationData.Current.LocalSettings when packaged, else a JSON file under %LOCALAPPDATA%/<ProcessName>/. That default is keyed on the process name, so renaming the exe strands saved layouts. Unpackaged apps can opt into a stable publisher/product identity before the first OpenWindow:
using Microsoft.UI.Reactor.Hosting.Persistence;ReactorApp.WindowPersistenceStore =new UnpackagedAppDataStore(publisher: "Contoso", product: "TimeTracker");
Opt-in rather than default: the two stores key data differently, so switching does not migrate existing layouts. Multiple instances sharing one publisher/product are safe — writes are serialized across processes, so one window's save cannot drop another's entry. Two windows sharing a PersistenceId still overwrite each other, by design.
Z-order, taskbar, and chrome
new WindowSpec{ShowInTaskbar = false,ShowInSwitcher = true,Level = WindowLevel.Floating, // Normal | Floating | AlwaysOnTopStyle = WindowStyle.ToolWindow, // Default | None | ToolWindowCornerStyle = WindowCornerStyle.RoundedSmall,};
Notes:
Floatingstays above owners and sibling Reactor app windows.ToolWindowhides from the taskbar by default unlessShowInTaskbaris explicit.WindowStyle.Noneshould normally setIsMovableByBackground = true.UseIsCovered()is a z-order hint, not pixel-accurate occlusion.
Title bar and backdrop
VStack(TitleBar("My App"),Body());
A TitleBar(...) element infers ExtendsContentIntoTitleBar = true when the spec value is null. Explicit true or false wins.
Close-safety caveat (#537):ExtendsContentIntoTitleBar = falsewith amountedTitleBar(...)is allowed and previously crashed on close(STATUS_HEAP_CORRUPTION); Reactor now flips the window back intocontent-extended mode just before the native close (every close/exit/disposepath) so it is safe. Prefer omittingTitleBar(...)when you want the systemtitle bar.
Title bar icon
ReactorApp.Run<App>("My App", icon: WindowIcon.FromPath("Assets/AppIcon.ico"));TitleBar("My App"); // inherits the window icon -- nothing to declareTitleBar("My App").NoIcon(); // opt out, for a deliberately bare title bar
A TitleBar(...) with no .Icon(...) inherits the window's icon: WindowSpec.Icon if declared, otherwise the Assets\AppIcon.ico convention beside the app. Do not restate the app icon on the title bar — that is the default. Use .Icon(...) only to show a different mark there.
Not inherited: an icon that exists only as an executable PE resource (<ApplicationIcon>), because that stage yields a raw HICON with no path and a XAML IconSource needs an image source; and an embedded window (WindowSpec.Embed), which never receives a window icon at all.
Icon sources
WindowIcon.FromPath("Assets/AppIcon.ico"); // file beside the appWindowIcon.FromResource("ms-appx:///Assets/A.ico"); // packaged resourceWindowIcon.FromBytes(icoOrPngBytes); // encoded data in memoryWindowIcon.FromRgba(pixels, 16, 16); // raw RGBA8, top-down
Not every surface takes every kind, because they need different primitives:
| Surface | FromPath | FromResource | FromBytes / FromRgba | |
|---|---|---|---|---|
| Window caption / Alt-Tab | yes | yes | no | |
| Tray icon, taskbar overlay, thumbnail toolbar | yes | no | yes | |
| Jump-list entry | unpackaged only | packaged only | no |
The three shell surfaces need a raw HICON (LoadImageW on a file, or CreateIconFromResourceEx on in-memory data), neither of which reads an ms-appx: URI. Jump lists need a Uri. AppWindow.SetIcon needs a filesystem path. An unusable source is skipped with a diagnostic, never thrown — on the window that means falling through to Assets\AppIcon.ico or the PE icon, so a binary icon: leaves the window no barer than declaring none.
Reach for the binary factories when the icon is an embedded resource, a download, or drawn at runtime: they avoid writing a temporary file. The bytes are copied at construction and held for the WindowIcon's lifetime.
Tall title bar
TitleBar("My App").WithNavigation(nav).PaneToggleButtonVisible(true).Tall();new WindowSpec { ExtendsContentIntoTitleBar = true, TitleBarHeight = WindowTitleBarHeight.Tall };
.Tall() / .HeightOption(WindowTitleBarHeight) (Standard / Tall / Collapsed) is the layout for a title bar hosting a back button or pane toggle. It sets both the system caption (AppWindow.TitleBar.PreferredHeightOption) and the WinUI title-bar control's own height — the control does not follow the caption, so setting one alone leaves them disagreeing. An explicit .Height(...) wins over the implied 48; WindowSpec.TitleBarHeight wins over the element.
Requires a content-extended window — the native setter throwsERROR_INVALID_STATEotherwise. Reactor applies it after its owncontent-extension flip and warns instead of throwing, so prefer this oversettingPreferredHeightOptionfrom aUseEffect.
Caption button theme
TitleBar("My App").PreferredTheme(isDark ? WindowTitleBarTheme.Dark : WindowTitleBarTheme.Light);new WindowSpec { TitleBarTheme = WindowTitleBarTheme.Dark };
WinUI does not carry an element's RequestedTheme to the system caption buttons (min/max/close), so a root themed opposite to the system keeps system-themed buttons. .PreferredTheme(WindowTitleBarTheme) (Legacy / UseDefaultAppMode / Light / Dark) writes AppWindow.TitleBar.PreferredTheme on mount and on change — no UseEffect. Opt-in: with nothing declared Reactor never writes the property, and removing a declaration restores the value the caption had before Reactor first applied one. WindowSpec.TitleBarTheme wins over the element. Unlike the height, it does not need a content-extended window.
Custom title-bar content and drag regions
TitleBar(...) accepts custom Content. Interactive controls are excluded from the drag region automatically (WinApp SDK ≥ 2.1.3). Override per element with .IsDragRegion(false) (force clickable) or .IsDragRegion(true) (force draggable), and add .AutoRefreshDragRegions() when the content changes across renders:
(TitleBar("Gallery") with{Content = HStack(8,AutoSuggestBox("", _ => {}).Width(200),Button(Icon(FontIcon("\uE713", fontSize: 16)), OnSettings).AutomationName("Settings").IsDragRegion(false)),}).AutoRefreshDragRegions();
VStack(...).Backdrop(BackdropKind.Mica);new WindowSpec { Backdrop = BackdropChoice.Of(BackdropKind.DesktopAcrylic) };
BackdropKind.Transparent falls back to no backdrop when unsupported by the referenced Windows App SDK.
Taskbar, displays, and pickers
var taskbar = UseWindow()!.TaskbarItem;taskbar.Description = "Exporting";taskbar.Progress.State = TaskbarProgressState.Normal;taskbar.Progress.Value = 0.25;var displays = UseDisplays();var nearest = ReactorDisplay.NearestTo(window.Position.X, window.Position.Y);var file = await UseFilePickerAsync(new FilePickerOptions(FileTypeFilter: [".txt"]));var folder = await UseFolderPickerAsync(new FolderPickerOptions());
Picker hooks must run on the owning window's UI thread and use the owning HWND; there is no arbitrary HWND parameter.
Recipe: Command Palette
PowerToys Run-style launcher. See samples/apps/command-palette-window/.
new WindowSpec{Style = WindowStyle.None,IsMovableByBackground = true,Level = WindowLevel.AlwaysOnTop,CornerStyle = WindowCornerStyle.Rounded,StartPosition = WindowStartPosition.CenterOnCurrent,ShowInTaskbar = false,ShowInSwitcher = false,};
Recipe: Tool Palette
Photoshop-style owned floating palette. See samples/apps/tool-palette/.
var main = ReactorApp.OpenWindow(new WindowSpec { Title = "Editor" }, () => new Editor());ReactorApp.OpenWindow(new WindowSpec{Title = "Tools",Owner = main,Style = WindowStyle.ToolWindow,Level = WindowLevel.Floating,CornerStyle = WindowCornerStyle.RoundedSmall,}, () => new ToolPalette());
Recipe: Media Player (aspect-locked)
new WindowSpec{Title = "Player",Width = 960,Height = 540,AspectRatio = 16.0 / 9.0,};UseWindow()?.SetAspectRatio(videoWidth / (double)videoHeight);UseWindowAspectRatio(16.0 / 9.0);