docs/architecture/decisions/ADR-005-feature-folder-controls.md
Accepted
With 77+ controls in the library, code organization becomes critical for maintainability. The structure must support:
Each control resides in Controls/{ControlName}/ directory:
Controls/
├── Button/
│ ├── Button.cs
│ └── Button.xaml
├── Card/
│ ├── Card.cs
│ └── Card.xaml
├── NavigationView/
│ ├── NavigationView.Base.cs
│ ├── NavigationView.Properties.cs
│ ├── NavigationView.Events.cs
│ ├── NavigationView.Navigation.cs
│ ├── NavigationView.TemplateParts.cs
│ ├── NavigationView.AttachedProperties.cs
│ ├── NavigationView.xaml
│ ├── NavigationViewItem.cs
│ ├── NavigationViewItemHeader.cs
│ └── NavigationViewItemSeparator.cs
One control = one folder with all related files.
Pattern: Single .cs + .xaml pair
Badge/
├── Badge.cs # Control implementation
└── Badge.xaml # Implicit style
Applies to: Button, Badge, Card, InfoBar, TextBox, etc. (50+ controls)
Pattern: Multiple partial class files organized by concern
NavigationView/
├── NavigationView.Base.cs # Core control logic
├── NavigationView.Properties.cs # 27 dependency properties
├── NavigationView.Events.cs # 7 routed events
├── NavigationView.Navigation.cs # Page navigation logic
├── NavigationView.TemplateParts.cs # Template part fields/binding
├── NavigationView.AttachedProperties.cs # Attached property definitions
└── NavigationView.xaml # Implicit style + template
Partial class naming: {ControlName}.{Concern}.cs
Applies to: NavigationView, ContentDialog, TitleBar
Pattern: Additional related controls in same folder
NavigationView/
├── NavigationView*.cs # Main control (6 files)
├── NavigationView.xaml
├── NavigationViewItem.cs # Item container
├── NavigationViewItemHeader.cs # Header item
├── NavigationViewItemSeparator.cs # Separator
├── NavigationViewContentPresenter.cs # Content host
├── INavigationView.cs # Interface
└── INavigationViewItem.cs # Item interface
Rationale: Tightly coupled types that are only used together.
Pattern: Subfolder for supporting types
ContentDialog/
├── ContentDialog.cs
├── ContentDialog.FocusBehavior.cs
├── ContentDialog.xaml
├── ContentDialogHost.cs
├── ContentDialogHostBehavior.cs
└── EventArgs/
├── ContentDialogButtonClickEventArgs.cs
├── ContentDialogClosingEventArgs.cs
└── ContentDialogClosedEventArgs.cs
When to use subfolder:
All controls use: Wpf.Ui.Controls namespace (regardless of folder depth)
// File: Controls/NavigationView/NavigationView.cs
namespace Wpf.Ui.Controls; // Flat namespace, not Wpf.Ui.Controls.NavigationView
// ReSharper disable once CheckNamespace
Trade-off: Folder structure does not match namespace.
IDE Configuration Required:
# .editorconfig
dotnet_diagnostic.IDE0130.severity = none # Suppress namespace/folder mismatch
NavigationView splits by logical concern:
ContentDialog splits by feature:
When to split:
How to name:
Don't split:
{ControlName}.cs # Simple control
{ControlName}.{Concern}.cs # Partial class with concern
{ControlName}.xaml # Implicit style ResourceDictionary
{TypePurpose}{ControlName}.cs # e.g., NavigationViewItem
{ControlName}{Purpose}.cs # e.g., ContentDialogHost
I{ControlName}.cs # Interface
{ControlName}{EventName}EventArgs.cs
Examples:
ContentDialogButtonClickEventArgs.csNavigatedEventArgs.csOne control = one folder under Controls/
Paired .cs + .xaml with matching names
Flat namespace Wpf.Ui.Controls for all controls
Partial class naming {ControlName}.{Concern}.cs
ReSharper suppress comment when namespace doesn't match folder:
namespace Wpf.Ui.Controls;
// ReSharper disable once CheckNamespace
Keep related types together in same folder when tightly coupled
Never nest control folders (keep flat under Controls/)
❌ Controls/Buttons/Button/
✅ Controls/Button/
Never use category-specific namespace
❌ namespace Wpf.Ui.Controls.Buttons;
✅ namespace Wpf.Ui.Controls;
Never split files arbitrarily (must have clear separation of concerns)
Never create more than 2 directory levels under Controls/
✅ Controls/ContentDialog/EventArgs/
❌ Controls/ContentDialog/EventArgs/Closing/
Easy to Find:
# Looking for Button control?
Controls/Button/Button.cs # Immediately obvious location
Clear Boundaries:
Controls/Button/Scalable:
Isolation:
Discoverability:
Refactoring:
.Properties.cs)Partial Classes:
Controls/
├── Buttons/
│ ├── Button/
│ └── ToggleButton/
└── Navigation/
└── NavigationView/
Rejected:
Controls/
├── Button.cs
├── Button.xaml
├── NavigationView.cs
└── NavigationView.xaml
Rejected:
Controls/
├── Button/
├── Input/
│ ├── TextBox/
│ └── NumberBox/
└── Navigation/
└── NavigationView/
Rejected:
Controls/{NewControl}/{NewControl}.cs with control class{NewControl}.xaml with implicit styleExample: Card becomes too large
Before:
Card/
├── Card.cs (500 lines)
└── Card.xaml
After:
Card/
├── Card.Base.cs (200 lines - core logic)
├── Card.Properties.cs (100 lines - dependency properties)
├── Card.Animation.cs (100 lines - animation logic)
└── Card.xaml
Refactoring steps:
Card.Properties.csCard.Animation.csCard.Base.cspartial class CardEach complex control folder includes README.md:
# NavigationView
Complex navigation container with 6 partial class files:
- **Base.cs** - Core control logic, template application
- **Properties.cs** - 27 dependency properties
- **Events.cs** - 7 routed events
- **Navigation.cs** - Page navigation, journal, back/forward
- **TemplateParts.cs** - Template part bindings
- **AttachedProperties.cs** - HeaderContent attached property
Related types:
- NavigationViewItem - Selectable item container
- NavigationViewItemHeader - Non-selectable header
- INavigationView - Public control interface