Resources & Resource Dictionaries

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

A XAML resource is any object, usually a brush, a color, a style, a template or a converter, that is declared once under a key and then reused from many places. Resources are how a WPF app gets one visual identity instead of hundreds of copy-pasted attribute values, and they are the mechanism behind light and dark themes. This page was written against .NET 10 LTS (latest patch 10.0.13) with C# 14, Visual Studio 2026 18.11.1, Windows App SDK 2.5.1 and CommunityToolkit.Mvvm 8.4.2 on Windows 10 and Windows 11. .NET 11 is at release candidate 2 (11.0.0-rc.2) and general availability is expected at .NET Conf, 10 to 13 November 2026; the .NET 11 WPF release notes list bug fixes only at the time of writing, so nothing on this page changes for it. WPF is the stack in focus; WinUI 3 and Windows Forms are compared in a table further down.

The running scenario is Bookshelf Desktop, with a BooksViewModel, an IBookRepository and an IDialogService registered in the Generic Host. Here those pieces are used to show where colors and styles live, how the app switches between a light and a dark theme at run time, and how a shared control library contributes a dictionary to the app. The XAML language itself (elements, attributes, type converters) is covered on the page titled XAML Language Fundamentals, and the markup extensions as syntax (including {Binding} and {x:Static}) on Markup Extensions & Namescopes; this page concentrates on what a resource is and how it is found. Styles, templates and triggers are the most common resources and have their own pages later in the section.

What a resource is

Every FrameworkElement and FrameworkContentElement has a Resources property of type ResourceDictionary, and so does Application. A ResourceDictionary is a dictionary from a key to an object that can be filled in XAML (elements with an x:Key) or in code. A resource is looked up when a markup extension such as {StaticResource Key} or {DynamicResource Key} asks for it, or when code calls FindResource. Because the dictionary can hold any .NET object, the same mechanism serves very different purposes:

Kind of resource Typical key Why it is a resource

Colors and brushes

Bookshelf.Brush.Surface

One definition drives every window, and a theme change is one swap.

Styles

{x:Type Button} (implicit) or Bookshelf.Style.Heading (keyed)

Sets of property values shared by many controls.

Data templates

a DataType (implicit key)

Tells WPF how to draw a view model type, for example a BookViewModel.

Converters and other helpers

BoolToVisibility

Instances created once and reused by bindings.

Geometry, fonts, thicknesses, doubles

Bookshelf.Spacing.Medium

Design tokens, so spacing and sizes are consistent.

Images and URIs

Bookshelf.Image.Cover

A single place for assets that several windows use.

Data sources, CollectionViewSource

BooksView

Declarative sorting and grouping next to the markup that uses it.

The rule that makes resources valuable is that a key is resolved at the point of use, walking outward from the element. That walk is described next.

Resource lookup order

When a reference is resolved, WPF searches for the key in a fixed order and stops at the first match:

  1. the Resources of the element that has the property being set;

  2. the Resources of its logical parent, then of that parent’s parent, and so on up to the root element (a Window or a Page);

  3. the Resources of the Application (Application.Current.Resources), which includes everything merged into it;

  4. the theme resource dictionaries (the control library theme files such as Themes/Generic.xaml and the OS-theme-specific dictionaries WPF ships);

  5. the system resources: SystemColors, SystemFonts and SystemParameters.

If nothing matches, a static reference fails when the XAML loads, and a dynamic reference leaves the property at its default value.

flowchart TD A["Property set to a resource reference"] --> B{"Key in the element Resources?"} B -- yes --> Z["Use that resource"] B -- no --> C{"Key in a logical parent Resources, up to the root?"} C -- yes --> Z C -- no --> D{"Key in Application.Resources, including merged dictionaries?"} D -- yes --> Z D -- no --> E{"Key in the theme dictionaries?"} E -- yes --> Z E -- no --> F{"Key in the system resources: SystemColors, SystemFonts, SystemParameters?"} F -- yes --> Z F -- no --> G["StaticResource: XamlParseException at load. DynamicResource: property keeps its default. FindResource: ResourceReferenceKeyNotFoundException"]

Three consequences follow, and the examples below rely on them:

  • Nearest wins. A resource defined on a Grid shadows a resource with the same key on the Window or in App.xaml, but only for the elements inside that Grid. This is the supported way to override a theme for one region.

  • The logical tree is walked, not the visual tree. Content inside a ContextMenu, ToolTip or Popup is not a visual child of the element that opened it; WPF compensates for the common cases, but a resource that works in a window and not in a popup is a sign that the lookup is not reaching the dictionary you expect. Declare shared resources at application level to avoid the question.

  • A dictionary’s own entries beat its merged dictionaries, and among merged dictionaries the last one listed wins. That rule is the basis of theming by swapping a merged dictionary (see below).

StaticResource and DynamicResource

Both markup extensions return the resource stored under a key. The difference is when.

Aspect {StaticResource Key} {DynamicResource Key}

When it is resolved

Once, while the XAML is loaded.

At run time, and again whenever the resource found under the key is replaced.

Forward references

Not allowed. The key must be defined earlier in the file (or in an outer scope), so order matters inside a dictionary.

Allowed. The resource may be defined later or may not exist yet.

Reacts to a changed resource

No. The element keeps the object it got at load time.

Yes. Replacing the entry in the dictionary, or swapping a merged dictionary, updates the element.

Where it can be used

As the value of any property or of a resource entry.

Only as the value of a dependency property (or of a Freezable property); it cannot set a plain CLR property.

Cost

Cheapest: a lookup at load, nothing afterwards.

A reference object is kept per use and re-evaluated on change, which adds memory and work when used thousands of times.

Missing key

XamlParseException while loading.

No exception; the property keeps its default value.

Typical use in Bookshelf

Fixed design tokens, converters, and BasedOn references.

Anything a theme can change: surface, text and border brushes, and system colors.

The practical rule is: use StaticResource by default, and DynamicResource for values that must follow a theme, a system setting (such as high contrast) or a runtime change. A StaticResource placed in a style can still point at a resource that a theme replaces later, but it will keep pointing at the old object, which is the most common reason a "half-themed" window appears after a theme swap.

Both extensions accept any resource key, not only strings. The keys WPF itself uses are listed here:

Key form Example and meaning

String

x:Key="Bookshelf.Brush.Surface". Use a namespace-like prefix so keys from libraries do not collide.

Type

x:Key="{x:Type Button}" or, equivalently in a style, only TargetType="Button". This is an implicit style: it is applied to every Button below it without a reference. It is not applied to derived types.

Data type

A DataTemplate with DataType="{x:Type vm:BookViewModel}" and no key is registered under an implicit DataTemplateKey.

Static member

x:Key="{x:Static SystemColors.WindowBrushKey}" overrides a system brush for the scope. System resource keys such as SystemColors.WindowBrushKey are objects, not strings.

Component key

ComponentResourceKey identifies a resource defined by a control library, so that Themes/Generic.xaml entries can be addressed from other assemblies. It is used by custom controls and is covered on the page titled User Controls & Custom Controls.

Resource options

A few attributes and rules apply to the entries themselves:

Setting Behavior

x:Key

Required for every entry in a ResourceDictionary except a Style with a TargetType, or a DataTemplate with a DataType, which get an implicit key. Keys must be unique inside one dictionary; a duplicate raises a parse error.

x:Shared

x:Shared="False" makes every lookup return a new instance instead of the one shared object. Needed for a resource that must not be shared, for example an element that can have only one parent. The default is shared, which is what you want for brushes and styles.

Freezing

Brushes, geometries and other Freezable objects in a dictionary can be frozen (made immutable and thread-safe), which lets WPF share them cheaply. Frozen resources cannot be modified later; DynamicResource inside a Freezable stops it from being frozen.

ResourceDictionary.Source

A URI to a XAML file that is merged or loaded as the dictionary. To combine a file with local entries, use MergedDictionaries rather than putting both on one dictionary.

Order of definition

For StaticResource, a resource must be declared before it is referenced inside the same file. Put colors first, brushes next, styles last.

Merged dictionaries

Putting every resource in App.xaml does not scale. A merged dictionary lets a dictionary include other dictionaries from separate XAML files. The files can then be organized by purpose, edited independently and reused by other projects.

In the Bookshelf app the resources are split into four files with a clear dependency order:

  • Resources/Colors.xaml: the fixed palette (accent, danger, success), as Color values;

  • Resources/Brushes.xaml: fixed brushes built from the palette;

  • Resources/Styles.xaml: implicit and keyed styles, which use both fixed brushes and theme brushes;

  • Themes/Light.xaml and Themes/Dark.xaml: the theme-dependent brushes, introduced in the theme section.

The three base files are shown first. Colors has no dependencies:

<!-- Resources/Colors.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Color x:Key="Bookshelf.Color.Accent">#0F6CBD</Color>
    <Color x:Key="Bookshelf.Color.OnAccent">#FFFFFF</Color>
    <Color x:Key="Bookshelf.Color.Danger">#C42B1C</Color>
    <Color x:Key="Bookshelf.Color.Success">#107C10</Color>

</ResourceDictionary>

Brushes uses the colors. A merged dictionary is loaded in isolation, so a StaticResource inside Brushes.xaml can only see keys defined earlier in the same file or in the dictionaries merged into that file. It does not see a sibling that App.xaml merges. The safe pattern is to merge the dependency inside the file that needs it:

<!-- Resources/Brushes.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Colors.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <SolidColorBrush x:Key="Bookshelf.Brush.Accent" Color="{StaticResource Bookshelf.Color.Accent}" />
    <SolidColorBrush x:Key="Bookshelf.Brush.OnAccent" Color="{StaticResource Bookshelf.Color.OnAccent}" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Danger" Color="{StaticResource Bookshelf.Color.Danger}" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Success" Color="{StaticResource Bookshelf.Color.Success}" />

</ResourceDictionary>

Styles refers to brushes the theme will supply later, so it uses DynamicResource for those and StaticResource for the fixed ones:

<!-- Resources/Styles.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Brushes.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <!-- Implicit style: applies to every TextBlock below this dictionary -->
    <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="{DynamicResource Bookshelf.Brush.OnSurface}" />
        <Setter Property="TextTrimming" Value="CharacterEllipsis" />
    </Style>

    <Style x:Key="Bookshelf.Style.Heading" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
        <Setter Property="FontSize" Value="20" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="Margin" Value="0,0,0,8" />
    </Style>

    <Style TargetType="Button">
        <Setter Property="Padding" Value="12,6" />
        <Setter Property="Background" Value="{DynamicResource Bookshelf.Brush.Surface}" />
        <Setter Property="Foreground" Value="{DynamicResource Bookshelf.Brush.OnSurface}" />
        <Setter Property="BorderBrush" Value="{DynamicResource Bookshelf.Brush.Border}" />
    </Style>

    <Style x:Key="Bookshelf.Style.PrimaryButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="{StaticResource Bookshelf.Brush.Accent}" />
        <Setter Property="Foreground" Value="{StaticResource Bookshelf.Brush.OnAccent}" />
        <Setter Property="BorderBrush" Value="{StaticResource Bookshelf.Brush.Accent}" />
    </Style>

</ResourceDictionary>

App.xaml merges the files, and the order again matters, because the last dictionary wins on a duplicate key:

<!-- App.xaml -->
<Application x:Class="Bookshelf.Desktop.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- No StartupUri: the main window is created by the Generic Host in OnStartup. -->
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- Slot 0: the theme. Replaced at run time by ThemeService. -->
                <ResourceDictionary Source="Themes/Light.xaml" />
                <ResourceDictionary Source="Resources/Brushes.xaml" />
                <ResourceDictionary Source="Resources/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <!-- Entries here beat everything merged above. -->
            <Thickness x:Key="Bookshelf.Spacing.Medium">12</Thickness>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Two details are worth stating plainly. First, each reference to a merged dictionary creates its own instance of that dictionary. In the example Brushes.xaml is loaded once for App.xaml and once more through Styles.xaml, so the brushes exist twice, as two different objects. That is harmless here, but a large dictionary merged from fifty UserControl files is parsed fifty times; merge heavy dictionaries once at application level and let the controls find them by lookup. Second, the relative Source="Colors.xaml" in these files is resolved against the file that contains it, which works inside one assembly; across assemblies a pack URI is required (below).

The ResourceDictionary API

Member Use

Source

Loads the dictionary from a XAML file URI (relative, or a pack URI).

MergedDictionaries

A Collection<ResourceDictionary> of dictionaries included in this one. The last entry has precedence over earlier ones, and the dictionary’s own entries have precedence over all of them.

Indexer, Add, Remove, Contains, Clear

Dictionary access by key; in code, the key is any object (a string, a Type, or a resource key).

Keys, Values, Count

Enumerate the entries of this dictionary only; merged entries are not included.

IsReadOnly

Indicates whether the dictionary rejects changes.

Light and dark themes with DynamicResource

The theme-dependent brushes live in two dictionaries that define the same keys with different values. At any moment exactly one of them is merged into the application, in slot 0. Every style and control that refers to these keys through DynamicResource repaints when the dictionary is replaced.

<!-- Themes/Light.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="Bookshelf.Brush.Surface" Color="#FFFFFF" />
    <SolidColorBrush x:Key="Bookshelf.Brush.SurfaceAlt" Color="#F3F5F7" />
    <SolidColorBrush x:Key="Bookshelf.Brush.OnSurface" Color="#1F2328" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Muted" Color="#57606A" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Border" Color="#D0D7DE" />
</ResourceDictionary>
<!-- Themes/Dark.xaml : same keys, different values -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="Bookshelf.Brush.Surface" Color="#1F2328" />
    <SolidColorBrush x:Key="Bookshelf.Brush.SurfaceAlt" Color="#2B3138" />
    <SolidColorBrush x:Key="Bookshelf.Brush.OnSurface" Color="#E6EDF3" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Muted" Color="#9DA7B3" />
    <SolidColorBrush x:Key="Bookshelf.Brush.Border" Color="#3D444D" />
</ResourceDictionary>

The main window uses only DynamicResource for theme brushes, and StaticResource for the fixed ones. The data context is a BooksViewModel supplied by the host, so bindings use {Binding} (WPF has no compiled x:Bind):

<!-- MainWindow.xaml -->
<Window x:Class="Bookshelf.Desktop.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Bookshelf Desktop" Width="900" Height="560"
        Background="{DynamicResource Bookshelf.Brush.Surface}">

    <Grid Margin="{StaticResource Bookshelf.Spacing.Medium}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <TextBlock Style="{StaticResource Bookshelf.Style.Heading}" Text="Books" />
            <TextBox Width="220" Margin="16,0,0,8" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
                     Background="{DynamicResource Bookshelf.Brush.SurfaceAlt}"
                     Foreground="{DynamicResource Bookshelf.Brush.OnSurface}"
                     BorderBrush="{DynamicResource Bookshelf.Brush.Border}" />
            <Button Margin="8,0,0,8" Content="Add book" Command="{Binding AddBookCommand}"
                    Style="{StaticResource Bookshelf.Style.PrimaryButton}" />
            <Button Margin="8,0,0,8" Content="Toggle theme" Command="{Binding ToggleThemeCommand}" />
        </StackPanel>

        <DataGrid Grid.Row="1" ItemsSource="{Binding Books}" AutoGenerateColumns="False" IsReadOnly="True"
                  Background="{DynamicResource Bookshelf.Brush.SurfaceAlt}"
                  Foreground="{DynamicResource Bookshelf.Brush.OnSurface}"
                  BorderBrush="{DynamicResource Bookshelf.Brush.Border}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="*" />
                <DataGridTextColumn Header="Author" Binding="{Binding Author}" Width="200" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The swapping itself is ordinary C#. The ThemeService replaces the dictionary in slot 0 by index, so the precedence of the merged list is unchanged, and it can read the Windows app-mode preference for the initial value. It is a user pattern, not an API: Microsoft Learn documents merged dictionaries but has no page that recommends a runtime theme switching recipe for WPF.

using System.Windows;
using Microsoft.Win32;

namespace Bookshelf.Desktop.Services;

public enum AppTheme { Light, Dark }

public interface IThemeService
{
    AppTheme Current { get; }
    void Apply(AppTheme theme);
    AppTheme ReadSystemPreference();
}

public sealed class ThemeService : IThemeService
{
    private const int ThemeSlot = 0;
    private static readonly Uri LightUri = new("Themes/Light.xaml", UriKind.Relative);
    private static readonly Uri DarkUri = new("Themes/Dark.xaml", UriKind.Relative);

    public AppTheme Current { get; private set; } = AppTheme.Light;

    public void Apply(AppTheme theme)
    {
        var merged = Application.Current.Resources.MergedDictionaries;
        var next = new ResourceDictionary { Source = theme == AppTheme.Dark ? DarkUri : LightUri };

        if (merged.Count > ThemeSlot)
        {
            merged[ThemeSlot] = next;      // replace in place: precedence order is preserved
        }
        else
        {
            merged.Insert(ThemeSlot, next);
        }

        Current = theme;
    }

    public AppTheme ReadSystemPreference()
    {
        // 0 means the user chose dark mode for apps; a missing value means light.
        const string key = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
        using var personalize = Registry.CurrentUser.OpenSubKey(key);
        return personalize?.GetValue("AppsUseLightTheme") is 0 ? AppTheme.Dark : AppTheme.Light;
    }
}

The view model drives it with an MVVM Toolkit command. BooksViewModel depends on the interfaces, never on Application, so it can be unit tested with fakes:

using System.Collections.ObjectModel;
using Bookshelf.Core;
using Bookshelf.Desktop.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Bookshelf.Desktop.ViewModels;

public sealed partial class BooksViewModel : ObservableObject
{
    private readonly IBookRepository _books;
    private readonly IDialogService _dialogs;
    private readonly IThemeService _theme;

    public BooksViewModel(IBookRepository books, IDialogService dialogs, IThemeService theme)
    {
        _books = books;
        _dialogs = dialogs;
        _theme = theme;
        _theme.Apply(_theme.ReadSystemPreference());
    }

    public ObservableCollection<BookViewModel> Books { get; } = [];

    [ObservableProperty]
    public partial string? SearchText { get; set; }

    [RelayCommand]
    private void ToggleTheme() =>
        _theme.Apply(_theme.Current == AppTheme.Light ? AppTheme.Dark : AppTheme.Light);

    [RelayCommand]
    private async Task AddBookAsync()
    {
        var created = await _dialogs.EditBookAsync(null);
        if (created is not null)
        {
            var saved = await _books.AddAsync(created, CancellationToken.None);
            Books.Add(new BookViewModel(saved));
        }
    }
}

The service is registered with the other services; the full host setup belongs to the page titled WPF Architecture & Application Model:

services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<BooksViewModel>();
services.AddSingleton<MainWindow>();
sequenceDiagram participant U as User participant VM as BooksViewModel participant TS as ThemeService participant R as Application.Resources participant E as Elements with DynamicResource U->>VM: ToggleThemeCommand VM->>TS: Apply Dark TS->>R: MergedDictionaries slot 0 = Dark.xaml R-->>E: resource changed, references re-evaluated E-->>U: window repainted with dark brushes

Things this pattern requires to work:

  • every themed value is reached through DynamicResource. A single StaticResource on a theme brush keeps the old color after the swap;

  • both theme files define exactly the same keys. A key missing from one file leaves the previous brush behind;

  • the whole dictionary is replaced, which produces a resource-changed notification for every key it contains;

  • the high contrast mode of Windows has its own palette; check SystemParameters.HighContrast and use the SystemColors brushes in that case instead of the custom theme.

Built-in theme support in .NET 9 and 10

Since .NET 9, WPF also ships a Fluent theme (the PresentationFramework.Fluent assembly) and a ThemeMode property on Window and Application with the values None, System, Light and Dark. The feature was introduced as experimental in .NET 9, which means it is guarded by the WPF0001 diagnostic and the API surface can still change, so check the What’s new in WPF page of the version you target before depending on it. The Bookshelf ThemeService above works on every supported version and is independent of it. Microsoft Learn describes ThemeMode in the API reference and the .NET 9 and 10 what’s-new pages, but has no Fluent theming deep dive; the custom brushes of this page are not themed by ThemeMode.

Pack URIs

A resource that lives in a different file, or a different assembly, is identified by a pack URI. WPF supports the pack scheme with two authorities:

Authority Meaning Example

application:/// (written application:,,, inside a pack URI)

Files that are part of the application, compiled into it or shipped with it.

pack://application:,,,/Resources/Styles.xaml

siteoforigin:/// (written siteoforigin:,,,)

A loose file at the location the application was started from (or, for a downloaded app, the site it came from). The file is not part of the assembly.

pack://siteoforigin:,,,/Plugins/theme.xaml

Within the application authority, three path forms exist:

Form Meaning

/Resources/Styles.xaml

A resource in the local assembly. In XAML attributes such as Source this relative form is the normal way to write it.

/Bookshelf.Controls;component/Resources/Shared.xaml

A resource in a referenced assembly (AssemblyName;component/Path).

/Bookshelf.Controls;v1.2.0.0;component/Resources/Shared.xaml

The same, restricted to a version of the referenced assembly (a public key token can follow the version for strong-named assemblies).

The commas in pack://application:,,,/ are the URI-escaped form of the slashes of application:///. A pack URI in code must be created as an absolute URI, new Uri("pack://application:,,,/Resources/Styles.xaml", UriKind.Absolute), because the pack scheme has to be registered first; touching Application.Current (or constructing any WPF Application) does that. View-model tests should not depend on pack URIs, because they need the WPF runtime.

Build actions: Resource, Content, Embedded resource

How a file is built decides how it is reached at run time. In SDK-style WPF projects (Microsoft.NET.Sdk with <UseWPF>true</UseWPF>), .xaml files are included automatically, so most XAML needs no configuration; images and data files do.

Build action What happens How it is reached Typical use

Page

The XAML is compiled to BAML and stored in the assembly resources. It is the default for .xaml files (except App.xaml, which is ApplicationDefinition).

A relative or component pack URI, Source= on a ResourceDictionary, Application.LoadComponent.

Windows, user controls, resource dictionaries.

Resource

The file is embedded in the assembly (in the .g.resources stream).

A pack URI such as pack://application:,,,/Assets/cover.png; Application.GetResourceStream.

Images, fonts, small assets that must travel with the assembly.

Content

The file is not embedded; it is copied beside the executable (set CopyToOutputDirectory), and an assembly attribute records the association.

The same pack URI form as Resource, resolved against the file next to the app. Application.GetContentStream.

Large or replaceable files (sample data, templates, documents).

EmbeddedResource

The file is embedded in the assembly as a manifest resource, but not in the WPF resource stream.

Not reachable with a pack URI. Use Assembly.GetManifestResourceStream and ResourceManager (this is where .resx files go).

Localized strings, binary blobs read by code.

None

The file is ignored by the build.

Not available at run time.

Documentation, design sources.

In the project file:

<!-- Bookshelf.Desktop.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>14</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- XAML files are Page / ApplicationDefinition by default: no entry needed. -->
    <Resource Include="Assets\Images\*.png" />                       <!-- compiled into the assembly -->
    <Content Include="Assets\Samples\books.json">                    <!-- copied next to the exe -->
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <EmbeddedResource Include="Assets\Licenses\THIRD-PARTY.txt" />   <!-- manifest resource -->
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Bookshelf.Controls\Bookshelf.Controls.csproj" />
  </ItemGroup>

</Project>

Choosing between them is mostly about trust and updates. Resource files are signed and versioned with the assembly and cannot be tampered with without breaking it, but they enlarge the assembly and are loaded into memory. Content files can be replaced after deployment, which is useful for sample data and unwanted for anything the app trusts. A siteoforigin file is outside the assembly and the package entirely, so treat its content as untrusted input.

Resources in code

Anything that XAML does with markup extensions has a code equivalent, found on FrameworkElement and Application:

API Behavior

element.FindResource(key)

Runs the full lookup order from element. Throws ResourceReferenceKeyNotFoundException when the key is not found.

element.TryFindResource(key)

The same lookup, returning null instead of throwing.

element.SetResourceReference(property, key)

Binds a dependency property to a resource key dynamically, exactly like DynamicResource in markup.

element.Resources[key]

Reads or writes only that element dictionary; does not walk the tree and does not find merged or application resources of parents.

Application.Current.Resources

The application dictionary, including everything merged into it. Use it for global access and for swapping merged dictionaries.

Application.Current.FindResource(key), TryFindResource(key)

Lookup starting at the application level (application, theme, system).

Application.LoadComponent(uri), Application.GetResourceStream(uri), Application.GetContentStream(uri)

Load a compiled XAML component, or open a stream for a Resource or Content file by pack URI.

A small control from the Bookshelf details pane shows the options. A status badge picks its brush from the application resources in code, registers a dynamic reference for the border, and loads a placeholder cover by pack URI:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Bookshelf.Desktop.Views;

public sealed class BookStatusBadge : Border
{
    private static readonly Uri PlaceholderCover =
        new("pack://application:,,,/Assets/Images/cover-placeholder.png", UriKind.Absolute);

    public BookStatusBadge(bool isOverdue)
    {
        Padding = new Thickness(8, 2, 8, 2);
        CornerRadius = new CornerRadius(4);

        // 1. A one-time lookup: the badge keeps this brush even if the theme changes later.
        //    TryFindResource avoids an exception when a resource dictionary is missing a key.
        var key = isOverdue ? "Bookshelf.Brush.Danger" : "Bookshelf.Brush.Success";
        Background = TryFindResource(key) as Brush ?? Brushes.Gray;

        // 2. A dynamic reference: equivalent to BorderBrush="{DynamicResource Bookshelf.Brush.Border}"
        SetResourceReference(BorderBrushProperty, "Bookshelf.Brush.Border");

        Child = new Image
        {
            Width = 16,
            Height = 16,
            Source = new BitmapImage(PlaceholderCover),   // build action: Resource
        };
    }

    // Reading the global dictionary, for example to show a design token in a dialog.
    public static Thickness MediumSpacing =>
        Application.Current.Resources["Bookshelf.Spacing.Medium"] is Thickness t ? t : new Thickness(8);
}

Two code-side pitfalls matter in practice. FindResource and TryFindResource need the element to be connected to the logical tree (or at least to have its resources reachable); calling them in a constructor before the element is loaded only sees the element own dictionary and the application, which is why this example uses application-level keys. And a value read once, as in the first case above, is not dynamic: use SetResourceReference (or set the property in XAML with DynamicResource) whenever the value must follow a theme.

Sharing dictionaries across assemblies

Shared design resources belong in a class library, so that Bookshelf Desktop, a future admin tool and the tests can all use them. The library, Bookshelf.Controls, is a WPF class library whose dictionaries are compiled as Page items and therefore stored in its assembly.

<!-- Bookshelf.Controls/Bookshelf.Controls.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
<!-- Bookshelf.Controls/Resources/Shared.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style x:Key="Bookshelf.Style.Card" TargetType="Border">
        <Setter Property="Padding" Value="12" />
        <Setter Property="CornerRadius" Value="8" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="{DynamicResource Bookshelf.Brush.Border}" />
        <Setter Property="Background" Value="{DynamicResource Bookshelf.Brush.SurfaceAlt}" />
    </Style>

</ResourceDictionary>

The application references the project and merges the dictionary with the AssemblyName;component form. The library dictionary refers to theme brushes with DynamicResource on purpose: it does not define them, and the host application supplies them (a StaticResource there would fail to load, because the keys are not in the library):

<!-- Bookshelf.Desktop/App.xaml : merged section only -->
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Themes/Light.xaml" />
    <ResourceDictionary Source="Resources/Brushes.xaml" />
    <ResourceDictionary Source="Resources/Styles.xaml" />
    <ResourceDictionary Source="/Bookshelf.Controls;component/Resources/Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- BookDetailsView.xaml uses the shared style -->
<Border Style="{StaticResource Bookshelf.Style.Card}">
    <StackPanel>
        <TextBlock Style="{StaticResource Bookshelf.Style.Heading}" Text="{Binding Title}" />
        <TextBlock Text="{Binding Author}" />
    </StackPanel>
</Border>

The same file can be loaded from code by its absolute pack URI, which is how a plug-in or a theme editor adds a dictionary at run time:

var shared = new ResourceDictionary
{
    Source = new Uri("pack://application:,,,/Bookshelf.Controls;component/Resources/Shared.xaml", UriKind.Absolute),
};
Application.Current.Resources.MergedDictionaries.Add(shared);

Library authors have a second, different mechanism: a custom control library puts its default styles in Themes/Generic.xaml, declares [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] and WPF finds them at the "theme" step of the lookup, with no merge in the app. A shared dictionary like the one above is for styles the application applies explicitly or implicitly to standard controls; theme files are for the default look of controls the library defines. The choice between a UserControl and a templated custom control is on the page titled User Controls & Custom Controls.

Resources across the Windows UI stacks

The concepts transfer to the other stacks, but the details differ. WinUI 3 uses the same idea with its own markup extensions, and Windows Forms has no XAML resources at all.

Aspect WPF WinUI 3 (Windows App SDK) Windows Forms

Dictionary type

System.Windows.ResourceDictionary

Microsoft.UI.Xaml.ResourceDictionary

None. .resx files and ResourceManager for strings and images.

Static reference

{StaticResource Key}

{StaticResource Key}

Not applicable (Properties.Resources.Name is a generated property).

Reacts to a change

{DynamicResource Key}

{ThemeResource Key}, which also reacts to the light, dark and high-contrast theme.

Set properties again in code, or use Application.SetColorMode (experimental in .NET 9, stable in .NET 10).

Theme variants in one dictionary

No. Use two dictionaries and swap them, as above.

Yes: ThemeDictionaries with Light, Dark and HighContrast entries inside one dictionary.

Not applicable.

Merge another file

Source="/Assembly;component/Path.xaml", relative, or pack://

Source="ms-appx:///Path.xaml" (the app package) or a relative path.

Not applicable.

Built-in control styles

Aero2 or Fluent (ThemeMode) dictionaries in the framework.

XamlControlsResources merged at application level.

System renderer.

Implicit styles

Yes, by type.

Yes, by type.

No; subclasses or code.

Application dictionary

Application.Current.Resources

Application.Current.Resources

Application has settings and Properties.Resources, not a style dictionary.

Build action for images

Resource or Content, pack URI

Content (copied into the package), ms-appx:/// URI

EmbeddedResource via .resx

Windows Forms supports a dark mode through Application.SetColorMode: experimental (diagnostic WFO5001) in .NET 9 and stable in .NET 10, where it no longer needs a NoWarn. It is documented in the Windows Forms what’s-new pages; Microsoft Learn has no equivalent of a resource dictionary there. Third-party frameworks such as Avalonia (labelled third-party) follow the WPF model with their own resource dictionary classes and URI schemes; this site does not cover them. The WinUI XAML model is introduced on the page titled WinUI 3 & the Windows App SDK, and the differences in markup between the XAML dialects on XAML Dialects: WPF, WinUI & MAUI.

From .NET Framework to .NET 8, 9 and 10

Resource dictionaries, pack URIs and the lookup rules are unchanged between .NET Framework 4.x and modern .NET, so existing XAML moves as it is. What changes is around them:

Area Change

Project format

Old projects list every file; SDK-style projects include .xaml as Page automatically, and only non-XAML assets need Resource or Content items. Migration tools convert the project file; the XAML stays.

Assembly references

AssemblyName;component pack URIs work as before, but ProjectReference and the target framework (net10.0-windows) must match, because a library built for net48 cannot be referenced from net10.0-windows without multi-targeting or porting.

.NET 8 and 9

Both leave support on 2026-11-10. .NET 9 added the experimental Fluent theme and ThemeMode.

.NET 10

LTS until 2028-11-14. Resource handling is unchanged. See the .NET 10 WPF what’s-new page for incremental improvements.

.NET 11

Release candidate 2; the WPF what’s-new page lists bug fixes only, no resource changes.

Trimming and Native AOT

Not supported for WPF and Windows Forms. This matters here because pack URIs and BAML loading depend on reflection and on resource names, which trimmers cannot analyze.

Pitfalls, performance and security

  • A StaticResource that is defined after its use fails at load. The error message is Cannot find resource named and the XAML designer shows an empty window. Move the definition earlier, or switch to DynamicResource.

  • Sibling merged dictionaries do not see each other. Keep each file self-sufficient by merging its own dependencies, or reference by DynamicResource. Do not rely on the order in App.xaml.

  • Implicit styles do not apply to derived types. A style for TargetType="Window" does not style MainWindow; use an explicit BasedOn style, or a style keyed by name.

  • Typos in DynamicResource are silent. Unlike StaticResource, a wrong key raises nothing. Search the Output window for resource-not-found traces while debugging, and keep key names in a naming convention.

  • Do not mutate a shared resource. Changing a brush Color that many elements share changes it everywhere; if you need a per-element variant, clone it (brush.Clone()) or use x:Shared="False".

  • Replace, do not edit, to retheme. Swapping a whole dictionary notifies every DynamicResource, whereas editing frozen objects in place throws.

  • Merging the same dictionary many times costs startup time and memory. Merge once at application level; for controls shipped as a library, prefer lookups over per-control merges.

  • DynamicResource everywhere is not free. Thousands of dynamic references in data templates add allocation and update work. Use StaticResource for tokens that never change, and consider freezing brushes.

  • Pack URI mistakes are runtime errors. A wrong relative path or a missing ;component produces an IOException or a XamlParseException only when the dictionary loads. Check the project file build action and assembly name. Add a smoke test that opens the main window.

  • Loose files are not trusted. siteoforigin and Content files can be replaced by a user or by malware with write access to the install folder. Never load XAML with XamlReader.Load from an untrusted source: XAML can instantiate arbitrary types and is an injection vector.

  • Accessibility. A custom theme must keep a contrast ratio that is readable; respect high contrast through SystemParameters.HighContrast and the SystemColors keys.

  • Localization is not resources of this kind. Strings belong in .resx (an EmbeddedResource) or in a dictionary per language; see the page titled Globalization & Localization.

What Microsoft Learn does not provide

  • The WPF documentation of resources has not been rewritten for modern .NET; the conceptual pages above are the same as for .NET Framework. They do not discuss .NET 10 specifics because there are none.

  • There is no official runtime theme-switching recipe for WPF and no guidance for swapping merged dictionaries. The ThemeService in this page is a user pattern that relies only on documented behavior (MergedDictionaries, DynamicResource).

  • ThemeMode and the Fluent theme are experimental; Learn documents the API but not a design walkthrough.

  • There is no official guidance on structuring dictionaries for large apps, or on key naming. The Bookshelf.Brush.* prefix is only a convention used in this section.

  • Performance advice about merged dictionaries and DynamicResource is community knowledge and should be measured on your own app.