Agent Skill · Dev Proxy

winui-design

WinUI 3 UI design and XAML correctness — layout planning, control selection, Fluent Design, theming (Light/Dark/HighContrast), typography styles, spacing, brushes, accessibility, data binding review. Use when designing new pages, converting from WPF/Electron/web, reviewing XAML, fixing theme issues, or applying Fluent Design.

Provider: Dev Proxy Path in repo: plugins/winui/skills/winui-design/SKILL.md

Skill body

UI Planning

Before picking controls, search the catalogue. This skill ships winui-search.exe alongside this SKILL.md. It indexes 100+ WinUI Gallery controls, every Windows Community Toolkit scenario, and a curated set of platform integration patterns (JumpList, Share, file pickers, drag-drop). Use it to ground every control choice in a real shipping sample before writing any XAML — this is the difference between guessing property names and copying canonical code.

.\winui-search.exe search "<feature 1>" "<feature 2>" ...   # batch one focused query per feature
.\winui-search.exe get <id 1> <id 2> ...                     # batch up to 3 IDs — full XAML + C# + pitfall notes
.\winui-search.exe list                                       # browse all patterns (heavy — prefer search)
.\winui-search.exe update                                     # force refresh now

Workflow: in one search call, list every feature you need for the current page (one focused query per feature, not a bag of keywords) → from each shortlist pick the best ID → grab full code with get (batch up to 3 IDs per call) → then write XAML using those samples as reference. Do NOT interleave searching with coding — front-load all lookups, then code. BM25 rewards focused per-query phrasing, so keep each query about one control or pattern.

Step 1: Identify App Type and Anchor Control

| App Type | Anchor Control | Reference App | |———-|—————|—————| | Settings / config tool | NavigationView Left + SettingsCard | Windows Settings | | Document / session editor | TabView + full-width content | Windows Terminal, Notepad | | Hierarchical browser | TreeView + ListView + BreadcrumbBar | File Explorer | | Developer tool / dashboard | NavigationView + card layout | Dev Home | | Single-purpose utility | Mode switcher + compact grid | Calculator |

Step 2: Map Requirements to Controls

Navigation: 2-7 sections → NavigationView; document 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 Grid column headers; 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 → TeachingTip; inline status → InfoBar; system notification → AppNotification.

Step 3: Plan Layout

Step 4: Size the Window to the App

WinUI 3 has no SizeToContent. Without an explicit size, Windows defaults the main window to ~1024×768 — oversized for most utilities. Size the window in MainWindow’s constructor; derive from the layout, not a generic.

Rubric. Width = widest row + 48 padding (24 each side), 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 check (ranges, not targets — derive yours from the rubric):

If your derived number is well below its range, you missed a row — re-check.

AppWindow.Resize takes physical pixels, not DIPs — multiply by the monitor’s DPI scale:

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;
        AppWindow.Resize(new SizeInt32((int)(460 * scale), (int)(860 * scale)));
    }
}

XamlRoot.RasterizationScale is null in the ctor and stale after AppWindow.Move, so [DllImport] is the cleanest path. Don’t try to size the window by setting Width/Height on the root Grid — that clips content, not the window.

If the user asks for UI validation, see winui-ui-testing Step 3.5 to verify the rubric against the visual checklist.

Step 5: Design Anti-Patterns

| ❌ Don’t | ✅ Do Instead | |———-|————–| | Centered floating card on background | Content fills window with padding | | Custom pill/segment tab switcher | NavigationView Top or SelectorBar | | Equal-width 50/50 column split | Fixed sidebar (300-360px) + flexible main | | Hardcoded colors (#FF0000) | {ThemeResource} brushes | | ScrollViewer around ListView | ListView has built-in scrolling | | Custom ControlTemplate for standard controls | Built-in controls with style overrides |

XAML Correctness

Theming Rules

<!-- Correct: StaticResource redirect in theme dictionary -->
<StaticResource x:Key="MyBrush" ResourceKey="ControlFillColorDefaultBrush" />

<!-- Wrong: inline SolidColorBrush allocates new object -->
<SolidColorBrush x:Key="MyBrush" Color="{StaticResource ControlFillColorDefault}" />

High Contrast

Only 8 system color brushes allowed in HC dictionaries:

Background Foreground Use Case
SystemColorWindowColorBrush SystemColorWindowTextColorBrush General content
SystemColorHighlightColorBrush SystemColorHighlightTextColorBrush Selected/hover
SystemColorButtonFaceColorBrush SystemColorButtonTextColorBrush Buttons
SystemColorWindowColorBrush SystemColorHotlightColorBrush Hyperlinks
SystemColorWindowColorBrush SystemColorGrayTextColorBrush Disabled content

HC prohibitions: No hardcoded colors, no opacity, no accent colors, no regular WinUI brushes, no SystemColor* in Light/Dark dicts. Use empty HC dict when WinUI defaults suffice. Set HighContrastAdjustment = None at app level.

Typography — Use Styles, Not Raw FontSize

| Style | Size | Weight | Use For | |——-|——|——–|———| | CaptionTextBlockStyle | 12px | Regular | Small labels, timestamps | | BodyTextBlockStyle | 14px | Regular | Body text (default — don’t set explicitly) | | BodyStrongTextBlockStyle | 14px | Semibold | Emphasized body text | | SubtitleTextBlockStyle | 20px | Semibold | Section headers, card titles | | TitleTextBlockStyle | 28px | Semibold | Page titles | | TitleLargeTextBlockStyle | 40px | Semibold | Large feature titles | | DisplayTextBlockStyle | 68px | Semibold | Hero text |

Use SemiBold, never Bold. Minimum 12px. BasedOn styles must not re-declare inherited properties.

Spacing and Layout

Remove Defaults

Don’t set WinUI default values — blocks future updates:

Acrylic Pairings

| Surface | Background | Border | |———|———–|——–| | Flyouts, tooltips | AcrylicBackgroundFillColorDefaultBrush | SurfaceStrokeColorFlyoutBrush | | UI surfaces | AcrylicBackgroundFillColorBaseBrush | SurfaceStrokeColorDefaultBrush |

Use BackgroundSizing="InnerBorderEdge" on bordered acrylic. ThemeShadow requires Translation="0,0,32" and 12px parent padding.

Data Binding

Bool negation and Visibility functions — define static methods in code-behind:

// In code-behind (e.g., MainPage.xaml.cs)
public static Visibility BoolToVisibility(bool value) =>
    value ? Visibility.Visible : Visibility.Collapsed;
public static Visibility InvertBoolToVisibility(bool value) =>
    value ? Visibility.Collapsed : Visibility.Visible;
public static bool IsNotBusy(bool isLoading) => !isLoading;
<!-- Usage in XAML -->
Visibility="{x:Bind local:MainPage.BoolToVisibility(ViewModel.IsLoading), Mode=OneWay}"
IsEnabled="{x:Bind local:MainPage.IsNotBusy(ViewModel.IsLoading), Mode=OneWay}"

❌ NEVER use Converter={x:Null} — it crashes at runtime.

Accessibility

Setting attached properties in code-behind — WinUI attached properties use static methods, NOT object initializer syntax:

using Microsoft.UI.Xaml.Automation; // required for AutomationProperties

// ❌ WRONG — object initializer doesn't work for attached properties
var btn = new Button { AutomationProperties = { AutomationId = "BtnSave" } };

// ✅ CORRECT — static setter method
var btn = new Button { Content = "Save" };
AutomationProperties.SetAutomationId(btn, "BtnSave");
AutomationProperties.SetName(btn, "Save button");
Grid.SetRow(btn, 1);
Grid.SetColumn(btn, 0);
ToolTipService.SetToolTip(btn, "Save the current document");

Formatting

References

File Read when…
references/approved-brushes.md Looking up correct WinUI brush names and usage rules
references/theme-aware-resources.md Implementing ThemeResource/StaticResource, High Contrast, acrylic pairings
references/code-review-checklist.md Reviewing XAML changes for correctness
references/pr-review-patterns.md Applying concrete review fixes and patterns
references/control-styles.md Customizing built-in control styles
references/typography-and-spacing.md Detailed type ramp, spacing grid, and sizing examples
references/colors-and-materials.md Theme brush catalog, Mica/Acrylic surface pairings, material usage
references/iconography-and-motion.md Icon guidelines, animation patterns, connected animations