Creating a Modern WPF Accordion Menu: Step-by-Step Guide

Customizing a WPF Accordion Menu with Styles and TemplatesA WPF accordion menu is a compact, familiar UI pattern that organizes content into expandable panels (accordion items) where only one—or a few—panels are open at once. Customizing an accordion in WPF gives you control over visuals, behavior, accessibility, and performance. This article walks through practical techniques to design, style, and template an accordion menu using WPF’s styling system, control templates, data templates, and MVVM-friendly practices.


What is an Accordion in WPF?

An accordion is usually implemented as a control that hosts multiple collapsible items. Microsoft’s WPF doesn’t ship with a built-in Accordion in the core framework, but several libraries include one (for example, the WPF Toolkit, Extended WPF Toolkit, or third-party UI suites). Alternatively, you can build your own by composing ItemsControl, Expander, or ToggleButton-derived controls. Regardless of source, customization follows similar principles: apply styles, override control templates, and use data templates for item content.


Choosing an Accordion Implementation

Before customizing, decide whether to use:

  • Third-party control (e.g., Extended WPF Toolkit, MahApps, Telerik, DevExpress) — provides ready accordion controls and features (selection modes, animations, virtualization).
  • Built-from-scratch with Expander or ToggleButton + ItemsControl — more flexible and lightweight; you control every aspect.
  • WPF Toolkit Accordion — a simple option for many apps.

If you need deep integration or consistent look with other custom controls, building your own accordion using ItemsControl + Expander or custom UserControl is often best.


Basic Accordion Markup (using Expander)

Here’s a simple starting point using Expander controls inside a StackPanel:

<StackPanel x:Name="AccordionPanel">     <Expander Header="Section 1" IsExpanded="True">         <TextBlock Text="Content for section 1..." Margin="8"/>     </Expander>     <Expander Header="Section 2">         <TextBlock Text="Content for section 2..." Margin="8"/>     </Expander>     <Expander Header="Section 3">         <TextBlock Text="Content for section 3..." Margin="8"/>     </Expander> </StackPanel> 

This is functional but unstyled. Next, we’ll improve visuals, transitions, and behavior.


Styling Basics: Colors, Spacing, and Typography

Start with a central ResourceDictionary to define brushes, spacing, and fonts:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">     <SolidColorBrush x:Key="AccordionBackground" Color="#FFF"/>     <SolidColorBrush x:Key="AccordionHeaderBackground" Color="#2B579A"/>     <SolidColorBrush x:Key="AccordionHeaderForeground" Color="White"/>     <SolidColorBrush x:Key="AccordionContentBackground" Color="#F3F6FB"/>     <Thickness x:Key="AccordionHeaderPadding">12,8</Thickness>     <CornerRadius x:Key="AccordionCornerRadius">4</CornerRadius>     <Style x:Key="AccordionHeaderText" TargetType="TextBlock">         <Setter Property="FontSize" Value="14"/>         <Setter Property="FontWeight" Value="SemiBold"/>     </Style> </ResourceDictionary> 

Apply these resources to your Expander headers and content to maintain consistency.


Creating a Reusable ControlTemplate for Expander

Customize the visual tree of each Expander by overriding its ControlTemplate. This lets you redesign headers, icons, and content layout.

<Style TargetType="Expander" x:Key="AccordionExpanderStyle">     <Setter Property="Template">         <Setter.Value>             <ControlTemplate TargetType="Expander">                 <Border Background="{StaticResource AccordionBackground}"                         CornerRadius="{StaticResource AccordionCornerRadius}"                         BorderBrush="#D0D7E6"                         BorderThickness="1"                         Margin="0,4,0,4">                     <DockPanel>                         <ToggleButton x:Name="HeaderToggle"                                       IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"                                       Background="{StaticResource AccordionHeaderBackground}"                                       Foreground="{StaticResource AccordionHeaderForeground}"                                       Padding="{StaticResource AccordionHeaderPadding}"                                       BorderThickness="0"                                       DockPanel.Dock="Top">                             <DockPanel>                                 <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" />                                 <Path x:Name="Arrow" Data="M 0 0 L 8 8 L 16 0 Z"                                       Width="16" Height="8" Fill="White"                                       HorizontalAlignment="Right" Margin="8,0,0,0"                                       RenderTransformOrigin="0.5,0.5"/>                             </DockPanel>                         </ToggleButton>                         <ContentPresenter x:Name="ContentSite"                                           Margin="8"                                           ContentSource="Content"                                           Visibility="Collapsed"/>                     </DockPanel>                 </Border>                 <ControlTemplate.Triggers>                     <Trigger Property="IsExpanded" Value="True">                         <Setter TargetName="ContentSite" Property="Visibility" Value="Visible"/>                         <Setter TargetName="Arrow" Property="RenderTransform">                             <Setter.Value>                                 <RotateTransform Angle="180"/>                             </Setter.Value>                         </Setter>                     </Trigger>                 </ControlTemplate.Triggers>             </ControlTemplate>         </Setter.Value>     </Setter> </Style> 

Notes:

  • The ToggleButton serves as header; binding to IsExpanded keeps visual state in sync.
  • The arrow rotates when expanded.
  • You can replace the Path with any icon (FontIcon, Image, or Glyph).

Smooth Expand/Collapse Animations

WPF’s default show/hide is instant. For a smooth height transition, animate a Clip rectangle or animate a TranslateTransform and Opacity. Here’s an approach using a Grid with RowDefinition.Height animated via a DoubleAnimation on a custom attached property (or use an Expander with an animated ContentPresenter).

Example using a Height animation with VisualStateManager (simplified):

<!-- inside ControlTemplate, replace ContentPresenter with this --> <Grid x:Name="ContentContainer" Height="0" ClipToBounds="True">     <ContentPresenter x:Name="AnimatedContent" ContentSource="Content" Opacity="0"/> </Grid> <ControlTemplate.Triggers>     <Trigger Property="IsExpanded" Value="True">         <Trigger.EnterActions>             <BeginStoryboard>                 <Storyboard>                     <DoubleAnimation Storyboard.TargetName="ContentContainer"                                      Storyboard.TargetProperty="Height"                                      From="0" To="150" Duration="0:0:0.25"/>                     <DoubleAnimation Storyboard.TargetName="AnimatedContent"                                      Storyboard.TargetProperty="Opacity"                                      From="0" To="1" Duration="0:0:0.2"/>                 </Storyboard>             </BeginStoryboard>         </Trigger.EnterActions>         <Trigger.ExitActions>             <BeginStoryboard>                 <Storyboard>                     <DoubleAnimation Storyboard.TargetName="ContentContainer"                                      Storyboard.TargetProperty="Height"                                      From="150" To="0" Duration="0:0:0.2"/>                     <DoubleAnimation Storyboard.TargetName="AnimatedContent"                                      Storyboard.TargetProperty="Opacity"                                      From="1" To="0" Duration="0:0:0.15"/>                 </Storyboard>             </BeginStoryboard>         </Trigger.ExitActions>     </Trigger> </ControlTemplate.Triggers> 

For variable content heights, measure the content and animate to the measured ActualHeight using code-behind or an attached behavior.


Data Templates and MVVM

When your accordion is data-driven (e.g., a collection of sections with title and content), use an ItemsControl with a DataTemplate for each item, keeping UI logic out of the view model.

ViewModel example:

public class AccordionItemViewModel {     public string Header { get; set; }     public object Content { get; set; } // or more specific type     public bool IsExpanded { get; set; } } public class MainViewModel {     public ObservableCollection<AccordionItemViewModel> Items { get; } = new(); } 

XAML using an ItemsControl:

<ItemsControl ItemsSource="{Binding Items}">     <ItemsControl.ItemTemplate>         <DataTemplate>             <Expander Header="{Binding Header}"                       IsExpanded="{Binding IsExpanded, Mode=TwoWay}"                       Style="{StaticResource AccordionExpanderStyle}">                 <ContentControl Content="{Binding Content}"/>             </Expander>         </DataTemplate>     </ItemsControl.ItemTemplate> </ItemsControl> 

To enforce single-open behavior (only one expanded at a time), handle it in the view-model:

private void OnItemExpanded(AccordionItemViewModel expanded) {     foreach (var item in Items)     {         if (!ReferenceEquals(item, expanded))             item.IsExpanded = false;     } } 

Raise OnItemExpanded when an item’s IsExpanded changes to true (use property setter logic or an event aggregator).


Accessibility Considerations

  • Ensure keyboard navigation: headers should be focusable (use Button/ToggleButton).
  • Use AutomationProperties.Name and ControlType for assistive tech.
  • Respect high-contrast themes by avoiding hardcoded colors; base brushes on SystemColors when appropriate.

Example:

<ToggleButton AutomationProperties.Name="{Binding Header}" /> 

Performance Tips

  • Virtualize large lists: if your accordion contains many items or heavy content, use virtualization (VirtualizingStackPanel) or load content on demand.
  • Use lightweight templates: avoid deep visual trees and unnecessary effects.
  • Defer complex content creation with DataTemplateSelectors or load-on-expand patterns.

Theming and Reuse

  • Keep styles in ResourceDictionaries so you can swap themes.
  • Expose DependencyProperties on a custom Accordion control (SelectionMode, AllowMultipleOpen, HeaderTemplate) for reuse.
  • Provide template bindings for key properties so app-level themes can override visuals.

Example: create a custom AccordionControl derived from ItemsControl that exposes AllowMultipleOpen as a property, then use a custom container (AccordionItem) derived from Expander to hook behavior.


Example: A Polished Accordion Style

A compact example tying pieces together:

  • ResourceDictionary defines brushes and paddings.
  • Expander style provides header ToggleButton with icon rotation.
  • ItemsControl binds to VM and uses DataTemplate.
  • Expand/collapse animation implemented via measured height in code-behind behavior for smooth transitions.

(Full code is long; combine snippets from sections above to assemble a complete working control. For measured-height animations, attach a behavior that measures content on load and animates the container’s height.)


Summary

Customizing a WPF accordion involves a mix of styling, control templating, data templating, and behavioral logic to meet your UX goals. Key steps:

  • Choose base implementation (third-party or custom).
  • Define reusable resources for consistent visuals.
  • Override ControlTemplate for complete visual control.
  • Add animations for polish, using measurement or behaviors for variable-height content.
  • Keep MVVM in mind: bind IsExpanded and implement single-open logic in view model.
  • Optimize for performance and accessibility.

Follow these guidelines and you’ll have a flexible, themeable accordion menu that fits your application’s needs.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *