--- name: winui-design description: "Use when designing, reviewing, or fixing WinUI 3: sample and control discovery with winapp find-ui, layout planning, control choice, Fluent Design alignment, Light/Dark/High Contrast theming, typography, spacing, brushes, accessibility, and XAML data-binding design. Load before authoring new XAML, reviewing UI PRs, migrating desktop UI to WinUI, or choosing between WinUI controls/patterns. Also use when asked to search WinUI samples, find a WinUI Gallery or Community Toolkit example, or find a control that does something." --- ## Search samples before writing XAML WinApp CLI 0.6+ provides grounded control and sample discovery through `winapp find-ui`. **Front-load lookups, then code**: ```powershell winapp find-ui "" # compact matches + scenario IDs winapp find-ui --id # full XAML/C# + prerequisite notes winapp find-ui --id --id --json # batch, structured output winapp find-ui --list # browse all default-source scenarios winapp find-ui "" --refresh # force a corpus refresh ``` Default search covers the WinUI Gallery, Windows Community Toolkit, and curated core patterns. Reactor's C#-only/MVU samples are opt-in with `--source reactor`; use them only for Reactor projects. The Gallery/Toolkit/Reactor corpus is fetched and cached by WinApp CLI, while core patterns work offline. ## App-shape anchors Pick the closest shipping app silhouette before laying out a page: | App type | Anchor controls | Reference apps | |----------|-----------------|----------------| | Settings / config tool | `NavigationView` Left + `SettingsCard` / `SettingsExpander` | Windows Settings, Slack | | Document / session editor | `TabView` + full-bleed content, light chrome | Windows Terminal, VS Code, Notepad | | Hierarchical browser | `TreeView` + `ListView` + `BreadcrumbBar` | File Explorer, Outlook | | Developer tool / dashboard | `NavigationView` + card layout | Dev Home, GitHub Desktop | | Single-purpose utility | Mode switcher + compact grid | Calculator, Snipping Tool | | Media / canvas / hero | `Grid` with hero surface, floating commands, **no** `NavigationView` | Photos, Spotify, Clipchamp | ## Reach-for-this control map Before writing XAML, map the requirement to a platform control. These mappings exist to short-circuit cross-framework instincts (WPF `DataGrid`, web ``): - **Navigation:** 2–7 sections → `NavigationView`; document/session tabs → `TabView`; breadcrumb trail → `BreadcrumbBar`; 2–3 modes → `SelectorBar`. - **Data display:** Vertical list → `ListView`; tiles/grid → `GridView` or `ItemsRepeater` + `UniformGridLayout`; hierarchy → `TreeView`; **tabular → `ListView` with a `Grid`-based `ItemTemplate` and a header `Grid` above** (WinUI has no `DataGrid`; don't default to `CommunityToolkit.WinUI.Controls.DataGrid` — its columns can't use `x:Bind`); master-detail → `ListView` + detail `Grid`. - **Input:** Text → `TextBox`; number → `NumberBox`; search → `AutoSuggestBox`; date → `CalendarDatePicker`; boolean → `ToggleSwitch`; pick one from 2–3 → `RadioButtons`; pick one from 4+ → `ComboBox`. - **Feedback:** Blocking decision → `ContentDialog`; contextual action → `Flyout` / `MenuFlyout`; onboarding / hint → `TeachingTip`; inline status / async progress → `InfoBar`; system notification → `AppNotification`. If the mapping above doesn't fit, run `winapp find-ui ""` before improvising. ## Window sizing (WinUI 3 specifics) > **WinUI 3 has no `SizeToContent`.** Without an explicit size, Windows defaults the main window to ~1024×768 — oversized for most utilities. Size it in `MainWindow`'s constructor. **Rubric.** Width = widest row + 48 padding, rounded up to nearest 20. Height = 32 (titlebar) + Σ(row heights) + Σ(spacing) + 48 padding, rounded up to 20. Round up — clipped content is a worse failure than a slightly-wide window. Sanity ranges (derive yours from the rubric): - Single-purpose utility → ~440–560 wide - Form / single-page tool → ~600–800 wide, ~640–800 tall - Multi-pane (nav + content) → ~1100–1300 wide, ~720–840 tall - Document / canvas / media editor → 1280+ wide `AppWindow.Resize` takes **physical pixels**, not DIPs — multiply by the monitor's DPI scale. `XamlRoot.RasterizationScale` is null in the constructor and stale after `AppWindow.Move`, so `[DllImport] GetDpiForWindow` is the cleanest path: ```csharp using Microsoft.UI; using Microsoft.UI.Windowing; using System.Runtime.InteropServices; using Windows.Graphics; public sealed partial class MainWindow : Window { [DllImport("user32.dll")] private static extern uint GetDpiForWindow(IntPtr hWnd); public MainWindow() { InitializeComponent(); var hwnd = Win32Interop.GetWindowFromWindowId(AppWindow.Id); var scale = GetDpiForWindow(hwnd) / 96.0; // widthDip / heightDip come from the rubric above — derive, don't copy. AppWindow.Resize(new SizeInt32((int)(widthDip * scale), (int)(heightDip * scale))); } } ``` Don't size the window by setting `Width`/`Height` on the root `Grid` — that clips content, not the window. ## XAML landmines (the things you'll otherwise ship broken) ### `x:Bind` defaults to `OneTime` ```xml ``` ### `TextBox` two-way needs `UpdateSourceTrigger=PropertyChanged` ```xml ``` Default trigger resolves to `LostFocus` specifically for `TextBox.Text` (most other properties default to `PropertyChanged`). The VM is not updated per keystroke, and UIA keyboard-simulation tests (WinAppDriver `SendKeys`, etc.) that assert immediately after typing will see stale VM state until focus moves. ### Attached properties from C# use static setters, not initializers ```csharp using Microsoft.UI.Xaml.Automation; // ❌ WRONG — does not compile. CS0117: 'Button' does not contain a definition for 'AutomationProperties'. // AutomationProperties is a static class of attached-property accessors, not an instance member. var btn = new Button { AutomationProperties = { AutomationId = "BtnSave" } }; // ✅ CORRECT var btn = new Button { Content = "Save" }; AutomationProperties.SetAutomationId(btn, "BtnSave"); AutomationProperties.SetName(btn, "Save button"); Grid.SetRow(btn, 1); ToolTipService.SetToolTip(btn, "Save the current document"); ``` ### `Converter={x:Null}` crashes `x:Bind` at runtime `{x:Bind}` requires `Converter` to be a `{StaticResource}` lookup. `Converter={x:Null}` compiles but the generated code calls `LookupConverter("")`, which returns null, then dereferences it — you get `Resource Dictionary Key can only be String-typed` / `NullReferenceException` on first activation of the binding. If you don't want a converter, omit the property entirely. ### Prefer `x:Bind` static functions over `IValueConverter` ```csharp // MainPage.xaml.cs public static Visibility BoolToVisibility(bool v) => v ? Visibility.Visible : Visibility.Collapsed; public static Visibility InvertBoolToVisibility(bool v) => v ? Visibility.Collapsed : Visibility.Visible; public static bool Not(bool v) => !v; ``` ```xml